Compare commits

...

177 Commits

Author SHA1 Message Date
IChooseYou
cb10bc8a82 docs: remove dedicated kernel driver section from README 2026-03-14 17:47:07 -06:00
IChooseYou
b5521bd638 docs: add kernel driver plugin to README
Document the KernelMemory plugin — capabilities, driver build
instructions, and architecture diagram.
2026-03-14 17:45:21 -06:00
IChooseYou
89d6e1944b fix: guard computeOffset against negative results before address arithmetic
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.
2026-03-14 17:31:13 -06:00
IChooseYou
7528d1bbbb Merge pull request #12 from 70RMUND/fix/linux-menubar-toolbuttons
fix: Linux menu bar horizontal layout via QToolButton fallback
2026-03-14 16:07:39 -06:00
Your Name
4f2288048e fix: Linux menu bar renders as horizontal tool buttons instead of collapsed extension popup
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.
2026-03-14 15:51:31 -04:00
IChooseYou
97b6f55e1f fix: Linux menu popups and SVG icon rendering
- 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
2026-03-14 12:41:11 -06:00
IChooseYou
6a30e0a402 fix: replace remaining QList::append({}) in plugins and tests
Missed plugin and test directories in the previous Qt 6.8 compat fix.
2026-03-14 12:11:08 -06:00
IChooseYou
1501a1542c feat: symbol double-click navigation, tree icons, and module.dll address parsing
- 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)
2026-03-14 11:57:32 -06:00
IChooseYou
4f82b39785 fix: uint64_t to QVariant ambiguity on Qt 6.8 Linux 2026-03-14 11:52:45 -06:00
IChooseYou
009ddc951c fix: commit remaining uncommitted source changes for CI
Add extractPdbSymbols declaration to import_pdb.h,
enumerateModules to provider.h, and other pending changes
that were only local.
2026-03-14 09:36:49 -06:00
IChooseYou
5921af2b4f fix: replace QList::append({}) with push_back/emplaceBack for Qt 6.8
Qt 6.8's stricter QList rejects brace-enclosed initializer lists in
append(). Fixed 43 call sites across 13 files.
2026-03-14 09:21:14 -06:00
IChooseYou
5ded192990 fix: sync tab title on keyword convert, add new screenshots
- Update dock tab title when converting enum/class via workspace menu
- Add tooltip and source picker screenshots to README
2026-03-14 09:14:28 -06:00
IChooseYou
54bee5022b fix: add missing symbols dock declarations to mainwindow.h 2026-03-14 09:03:41 -06:00
IChooseYou
5d2d324946 fix: add missing symbol store and PDB debug info sources
These files were referenced in CMakeLists.txt and main.cpp but
never committed, breaking the CI build.
2026-03-14 08:13:58 -06:00
IChooseYou
5b2cf1ae1f feat: arrow tooltip improvements and base address cheat sheet
- 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)
2026-03-14 08:03:23 -06:00
IChooseYou
f1a36f2ad3 feat: custom arrow tooltip with transparent background
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).
2026-03-14 06:45:45 -06:00
IChooseYou
665138e688 fix: force all nodes collapsed on file load
Prevents 512-element arrays from expanding on load and triggering
thousands of memory reads. Root nodes still show children via
isRootHeader override.
2026-03-14 06:05:53 -06:00
IChooseYou
7688bb5b92 ci: add SDK include paths for WDK NuGet driver build
NuGet splits WDK and SDK into separate packages. specstrings.h
lives in the SDK shared headers. Add SDK_INC_ROOT for shared/ucrt.
2026-03-14 05:40:24 -06:00
IChooseYou
701e088be8 ci: install WDK via NuGet for driver build in CI
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.
2026-03-14 05:20:23 -06:00
IChooseYou
3c0c248d54 fix: use delayed expansion in build_driver.bat for CI
Parentheses in "Program Files (x86)" broke cmd parser inside
for loop bodies. Switch to !var! delayed expansion.
2026-03-14 04:56:46 -06:00
IChooseYou
7af969f6bd ci: build kernel driver and include rcxdrv.sys in release
Add build_driver.bat step to Windows CI using runner's MSVC + WDK.
Copy rcxdrv.sys into Plugins/ in the release zip.
2026-03-13 15:27:51 -06:00
IChooseYou
8ba1fd2492 fix: auto-detect MSVC and WDK paths in build_driver.bat
Remove hardcoded MSVC 14.39.33519 and WDK 10.0.22621.0 paths.
Now scans for the newest installed version automatically.
2026-03-13 15:05:59 -06:00
IChooseYou
b08736245b feat: kernel memory plugin + unified source menu + driver improvements
- 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
2026-03-13 14:46:22 -06:00
IChooseYou
7f7bbdcc45 fix: remove isRelative references from generator.cpp
Node::isRelative is not yet in the pushed core.h, breaking CI builds.
2026-03-12 18:07:34 -06:00
IChooseYou
79b5125229 fix: remove unreleased isRelative/ToggleRelative references from controller
These were local-only changes that referenced cmd::ToggleRelative and
Node::isRelative which don't exist in the pushed core.h, breaking CI.
2026-03-12 16:30:59 -06:00
IChooseYou
3aeb1a80d5 feat: inline hex byte and ASCII preview editors for hex nodes
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.
2026-03-11 16:01:37 -06:00
IChooseYou
3b7ed682ac Merge commit 'refs/pull/11/head' of github.com:IChooseYou/Reclass
# Conflicts:
#	src/mcp/mcp_bridge.cpp
2026-03-10 16:02:12 -06:00
IChooseYou
0582cb286b fix: commit missing selectPage() for OptionsDialog 2026-03-10 15:43:27 -06:00
IChooseYou
ea85b7a621 feat: add C# and Python ctypes code generators
- C# backend: [StructLayout(LayoutKind.Explicit)] with [FieldOffset], IntPtr pointers, fixed arrays, enums
- Python backend: ctypes.Structure with _fields_, POINTER() for typed pointers, c_void_p, padding
- Both support enums, vectors, bitfields, arrays, unions, static fields
- Export menu: C# Structs... and Python ctypes... entries
- Format combo auto-populates new options
- 14 new tests for both backends (all passing)
2026-03-10 15:20:56 -06:00
IChooseYou
6c8b7d3d97 feat: Rust/#define generators, code tab format/scope combos, enum #define support
- 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
2026-03-10 15:05:23 -06:00
IChooseYou
d1321b5165 fix: per-group sentinel docks, editor inline-edit comment alignment
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.
2026-03-09 11:44:55 -06:00
IChooseYou
483f87cfbd feat: type hints green [bracketed] notation, workspace cleanup, unique naming
- Type inference hints now show value-first with bracketed type in comment
  green: "0x7ff718570000 [ptr64]", "6, 16 [int32_t×2]"
- Raise hint threshold to strong-only (score >= 75%)
- Remove Bool inference, widen Int16 range to ±16384
- Workspace: remove dead WorkspaceProxy, fix null deref, debounce search,
  cache icons, add pinning support
- Unique naming: UnnamedClass0/UnnamedEnum1 with global counter
- Footer buttons: +10h +100h +1000h replacing +1024
- MCP: project lifecycle API, snapshot provider fix
2026-03-09 10:39:22 -06:00
noita-player
4d0782db68 MCP bridge: support multiple concurrent clients
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.
2026-03-08 22:44:47 -07:00
noita-player
51de48a6ed Add MCP scanner tools, source.modules, reconnect, and constraint regions
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.
2026-03-08 22:44:46 -07:00
noita-player
7b9b140823 Fix MCP use-after-free, scanner chunk overlap, build scripts
- 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
2026-03-08 22:44:36 -07:00
IChooseYou
a21e5a07a8 feat: replace +1024 footer button with +10h +100h +1000h granular grow
- Three hex-sized grow buttons: +10h (16B), +100h (256B), +1000h (4096B)
- Single-space gaps between buttons for tighter layout
- All click, hover, cursor, and pill styling updated
- Enum +10 button unchanged and correctly disambiguated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:38:03 -06:00
IChooseYou
25afbe373b feat: status bar format, tab titles with source, taller tabs, pill hover, source switch base fix
- 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
2026-03-08 16:29:12 -06:00
IChooseYou
6a4cb47ed4 fix: kill Fusion outline on QScintilla, type inference hints, workspace styling
- 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
2026-03-08 10:26:12 -06:00
IChooseYou
431e2b90c9 perf: TypeSelector — zero-alloc fuzzy scorer, warm popup 75% faster
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.
2026-03-08 08:33:21 -06:00
IChooseYou
43365c1aff fix: close project actually destroys dock, editor perf single-pass line attributes
- 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
2026-03-08 08:13:36 -06:00
IChooseYou
596f410b96 perf: compose 30% faster — move semantics, BFS offsets, zero-alloc hex formatting
- compose.cpp: emitLine takes LineMeta&& (move, not copy) at all 22 call sites
- compose.cpp: reserve meta/text buffers, BFS offset computation O(N) vs O(N*D)
- compose.cpp: pre-compute typeNameLens[], merge global width loops
- format.cpp: bytesToHex uses stack buffer + lookup table (zero heap allocs)
- format.cpp: hexVal single QString::asprintf instead of 2-string concat
- editor.cpp: guard hover updates during applyDocument (stale index safety)
- core.h: assertion on makeArrayElemSelId negative index
- format.cpp: assertion on extractBits overflow
- main.cpp: tree lines enabled by default
- bench_large_class: add 2000-field benchComposeLarge test

Benchmark: 500 fields 0.70→0.51ms (27%), 2000 fields 2.28→1.57ms (31%)
2026-03-08 07:28:26 -06:00
IChooseYou
f0fc85f60f fix: CI test failures from collapsed=true default
- 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
2026-03-07 11:58:08 -07:00
IChooseYou
70c7404556 fix: MSVC build support, modern theme, vergilius fnptr import
- CMake: detect MSVC↔MinGW Qt ABI mismatch at configure time (#10)
- CMake: add /utf-8 /MP for MSVC builds
- CMake: fix theme/example deployment for multi-config generators (MSVC)
- Auto-run windeployqt post-build so correct Qt DLLs are always deployed
- Add Modern theme (dark blue with cyan/purple/amber accents)
- Vergilius import: handle function pointer typedefs
2026-03-07 11:31:04 -07:00
IChooseYou
f27459c21b fix: default collapsed=true for child structs, dock border wraps panel, search bar borderless, title bar +2px 2026-03-07 11:17:35 -07:00
IChooseYou
a5abcbeea6 Merge pull request #9 from noita-player/feature/peb-teb-mcp
Add process.info MCP tool for PEB/TEB enumeration and peb/tebs API for providers to implement
2026-03-07 09:42:51 -07:00
IChooseYou
7071402319 fix: workspace panel — preserve expansion on clear, dock title counts, drop kind text, close.svg clear button 2026-03-07 08:37:15 -07:00
IChooseYou
0dc390ed86 fix: WinDbg plugin dynamic dbgeng loading, editor two-tone bg, UI polish
WinDbg plugin: load dbgeng.dll dynamically from Debugging Tools directory
instead of static linking (system dbgeng.dll lacks remote DebugConnect).
Copy tools dbghelp.dll next to exe so it loads before System32 version.
Add COM init on DbgEng thread, browse for tools dir, styled dialog.

Editor: derive darker background via theme.background.darker(115) for
visual depth between chrome and editor surfaces.

UI: global scrollbar styling, workspace accent bar 1px, pane tab font
from editor settings, workspace dock default width 128px.
2026-03-07 08:31:51 -07:00
IChooseYou
188c27c6e2 feat: workspace panel visual overhaul, perf optimizations, remove kernel base addresses
Workspace panel:
- Custom WorkspaceDelegate: struct names bright, metadata dimmed, child types in teal
- Search box: monospace font, search icon, bordered with focus highlight
- Selection: accent bar, all fonts synced to 10pt monospace
- Remove rebuildWorkspaceModel from visibilityChanged (fixes double-click refresh)
- Incremental sync (syncProjectExplorer) preserves tree expansion state

Performance:
- childrenOf() O(1) via cached parent→children hash map
- Debounced workspace rebuilds (50ms coalesce)
- Pre-reserve node vector in NodeTree::fromJson
- Benchmark suite (bench_project)

Data:
- Remove kernel baseAddress from Vergilius/WinSDK examples (default to 0x400000)
2026-03-07 06:47:16 -07:00
noita-player
81f1e4319f Add process.info MCP tool for PEB/TEB enumeration
Expose PEB address via provider interface and query it in the
ProcessMemory plugin using NtQueryInformationProcess. The new
process.info MCP tool returns the PEB VA and enumerates TEBs by
querying thread information via NtQuerySystemInformation and
NtQueryInformationThread for each thread in the target process.
2026-03-06 23:21:10 -08:00
IChooseYou
3ab6affa5e fix: vergilius fnptr import, remove tab pin, flatten workspace tree, middle-click close
- Fix vergilius_to_rcx.py to detect function pointer syntax (*Name)(params) and emit FuncPtr64
- Re-fetch 85 structs to recover proper field names (697/716 fixed)
- Remove pin button from dock tabs and all pin-related context menu items
- Fix newClass() creating duplicate tabs
- Set workspace tree font to match tab bar (size 10)
- Flatten workspace tree: remove redundant Project group node (VS Code Explorer style)
- Add middle-click to close dock widget tabs
- Allow type chooser to show cross-doc types for root nodes
2026-03-06 17:39:50 -07:00
IChooseYou
35b3cd9ac1 feat: enum editing UI, protect enums from struct ops, New Class opens two tabs
- New Class creates two Unnamed tabs, selects the first
- New Enum creates 5 placeholder members (Member0-4)
- Right-click enum member: Add Member Above/Below, Remove Member
- Right-click enum header: Add Member, Rename, Delete only
- Enum nodes fully protected from struct operations (no Add Child, Insert, Convert)
2026-03-06 11:00:06 -07:00
IChooseYou
e5938f7e82 fix: enable hover on dock tab bars via WA_Hover attribute 2026-03-06 09:45:23 -07:00
IChooseYou
03c49d19dd fix: type chooser always shows modifiers, tabs show class name, dock buttons restored on re-dock 2026-03-06 09:23:36 -07:00
IChooseYou
b7eebedf50 fix: remove grab_tabs test target (missing source file) 2026-03-06 08:23:09 -07:00
IChooseYou
9ff456a8d6 revert: remove theme xcopy to avoid clobbering custom themes 2026-03-06 08:22:40 -07:00
IChooseYou
580f285edd fix: also copy theme JSON files to output dir for MSVC builds 2026-03-06 08:22:02 -07:00
IChooseYou
d23a6c7656 fix: copy example .rcx files to output dir for MSVC builds 2026-03-06 08:20:33 -07:00
IChooseYou
25d8de95b7 fix: crash in dismissStartPage due to re-entrant close/rejected signal 2026-03-06 08:16:13 -07:00
Sen66
955db3813a fix: msvc build due to startpage.h 2026-03-06 16:10:54 +01:00
IChooseYou
f4f203e0f0 Merge remote-tracking branch 'origin/fix-msvc-build' 2026-03-06 08:07:57 -07:00
IChooseYou
1d3f1a672a fix: start page card order, icon consistency, and Continue placement 2026-03-06 08:07:27 -07:00
Sen66
da29206bdb fix: msvc build with latest dock header file 2026-03-06 16:03:54 +01:00
IChooseYou
4986893fca feat: VS2022-style start page popup with recent files and get started cards 2026-03-06 07:58:13 -07:00
IChooseYou
17a1fb032e chore: remove Demo.rcx, add WinSDK + windows-x86_64.h examples 2026-03-06 07:56:33 -07:00
IChooseYou
8d92957837 fix: move DockTabButtons to header for MSVC automoc compatibility
automoc doesn't generate main.moc on MSVC, breaking the build.
Move DockTabButtons (which needs Q_OBJECT) to its own header so
automoc handles it as moc_dock_tab_buttons.cpp instead.
2026-03-06 06:14:59 -07:00
IChooseYou
f981fe456d feat: see-through popup dismiss for disasm/value-history/struct-preview
Override mouseMoveEvent in all three popup classes to forward mouse
position back to viewport hover logic. When the row underneath the
popup represents a different node, the popup dismisses automatically,
allowing rapid swiping through FuncPtr rows.
2026-03-05 18:25:40 -07:00
IChooseYou
877ceea4c1 feat: VS-style dock tabs with middle-elision and full context menu
- Remove stylesheet from dock tab bars; handle all painting in
  MenuBarStyle (CE_TabBarTabShape + CE_TabBarTabLabel) so middle-
  elision actually works (QStyleSheetStyle was intercepting labels)
- Accent line on selected tab, dark background, bottom border
- Tab font synced with editor font for correct sizing
- Full right-click context menu: Close, Close All Tabs, Close All
  But This, Close All But Pinned, Copy Full Path, Open Containing
  Folder, Float/Dock, Pin/Unpin Tab, New Horizontal/Vertical
  Document Group
- Add View → Reset Windows to re-tabify all docks
- Remove old View → Split/Remove Split
- Guard deferred timer lambdas with QPointer<QDockWidget>
- Extract setupDockTabBars() for idempotent tab bar configuration
- Register close-all.svg and split-vertical.svg icons
2026-03-05 15:16:01 -07:00
IChooseYou
4160a229c6 feat: workspace double-click opens struct in new tab + flat tab corners
- Double-clicking a root struct in the workspace tree opens it in a new
  tab (dock) sharing the same document, focused on that struct
- If a tab already views that struct, raises it instead of duplicating
- Child member double-click still navigates within the existing tab
- Doc lifecycle ref-counted: only deleted when last tab referencing it closes
- rebuildAllDocs/rebuildWorkspaceModel deduplicate shared docs
- Removed border-radius from all tab bar stylesheets (flat corners)
2026-03-05 13:49:42 -07:00
Sen66
1e1afc1640 fix: docking of 'project' window 2026-03-05 19:47:18 +01:00
IChooseYou
f0cf6c549a revert: restore .NET CLR hosting description for ReClass.NET plugin 2026-03-05 06:37:56 -07:00
Sen66
683eab16ee fix: better fix to switch to newly created class 2026-03-05 14:25:49 +01:00
Sen66
b53dea8f9f fix crash on application close 2026-03-05 14:25:06 +01:00
Sen66
f06abbab79 fix: on new class, switch to it 2026-03-05 14:23:07 +01:00
Sen66
2477591ed2 fix: assertion due to undo history disabled nullptr 2026-03-05 14:21:07 +01:00
IChooseYou
6c13356d6d docs: trim README plugin descriptions 2026-03-05 06:07:37 -07:00
IChooseYou
3b273a7ab2 fix: don't skip Array in scope width calc — only skip Struct
Array headers like int32_t[10] render in the type column and need
their width accounted for. Only Struct (pointer headers) should be
excluded from inflating sibling column widths.
2026-03-05 06:02:43 -07:00
IChooseYou
3509a0d9dd Merge remote-tracking branch 'origin/floating' 2026-03-05 05:58:18 -07:00
Sen66
43c3f5a842 fix: highlight issue between command row & opening brace 2026-03-05 13:52:40 +01:00
Sen66
0697ce4853 feat: option to have class opening brace on new line 2026-03-05 13:48:26 +01:00
IChooseYou
ed1bfd04cd fix: tighten editor column spacing — skip struct/array in scope width calc
Reduce kMinTypeW from 8 to 7, and exclude Struct/Array children from
per-scope column width measurement so pointer headers don't inflate
sibling hex row padding.
2026-03-05 13:48:26 +01:00
IChooseYou
c275eb33c9 fix: tighten editor column spacing — skip struct/array in scope width calc
Reduce kMinTypeW from 8 to 7, and exclude Struct/Array children from
per-scope column width measurement so pointer headers don't inflate
sibling hex row padding.
2026-03-05 05:46:14 -07:00
Sen66
636176ee8c feat: floating windows like old windbg 2026-03-05 13:23:00 +01:00
IChooseYou
9a716444f4 fix: menu border clipping, context menu cleanup, workspace sort
- Use WA_TranslucentBackground on QMenu popups so DWM doesn't clip
  border edges; draw 1px border at true widget edge via drawLine
- Move Insert 4/8 into Insert submenu, reorder context menu sections
- Sort workspace tree by visible (non-hex-pad) children count
2026-03-05 04:59:25 -07:00
Sen66
a46da4ee16 fix: horizontal scrollbar calculations for C/C++ view
- added msvc define NOMINMAX so we can use std::max
2026-03-05 12:46:55 +01:00
Sen66
cd52451210 fix: Release build configuration on MSVC & add windeployqt post-build 2026-03-05 12:16:11 +01:00
IChooseYou
82bf9118c9 feat: options dialog cleanup, menu/tree styling, light theme contrast
- Remove dead "Safe Mode" option, rename title case to "Uppercase menu items"
- Options tree: icons, themed hover/selection, mouse tracking (matches workspace tree)
- Tree item row padding (+4px) via MenuBarStyle CT_ItemViewItem for all trees
- Titlebar grows 2px when icon shown
- Menu popups: custom separator drawing, opaque background fill, flat hover highlight
- Menu bar/popup hover uses accent color (QPalette::Highlight) instead of grey
- Light theme: bump textMuted/textFaint contrast
- Dock grip widget for workspace and scanner docks
2026-03-04 13:44:42 -07:00
IChooseYou
f4c7e9327d fix: audit cleanup — themed close button, stale popup dismiss, bitfield clamp, scanner guard, process sort 2026-03-04 11:15:04 -07:00
IChooseYou
5944dbdc81 fix: cast char16_t to uint for QString::arg on macOS 2026-03-04 10:37:18 -07:00
IChooseYou
b3425aec9e clean up README: move screenshots above features, trim sections 2026-03-04 10:34:39 -07:00
IChooseYou
2a8cfee719 docs: update README screenshots (Windows, macOS, scanner) 2026-03-04 10:22:58 -07:00
IChooseYou
e999c664b8 feat: tree lines, scanner improvements, themes, tooltips, README overhaul
- Tree line connectors (Unicode box-drawing ├─ └─ │) at arbitrary depth
- Fix editor overwriting tree chars at depth 2+ (applyMarginText Pass 2)
- Scanner: unknown value scan, comparison rescan modes (Changed/Unchanged/Increased/Decreased)
- New Tailwind theme (tw.json), WCAG contrast fixes for warm/mid themes
- Tooltip system (rcxtooltip.h)
- Comprehensive README rewrite with full feature inventory
- New tests for compose tree lines, scanner, tooltips

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:21:09 -07:00
Lab
0dc4af6b1d Merge pull request #7 from IChooseYou/bundle-mcp-bridge
Bundle ReclassMcpBridge into macOS .app
2026-03-03 15:45:07 -08:00
Lab
376aad2169 Bundle ReclassMcpBridge into macOS .app
Copy the MCP stdio bridge executable into Reclass.app/Contents/MacOS/
via a POST_BUILD step so Claude Desktop can find it when the app is
distributed as a bundle.
2026-03-03 15:43:37 -08:00
Matty
4937c58062 fix: grey out value input instead of hiding, raise unknown scan cap to 10M 2026-03-03 12:16:14 -07:00
Matty
9c72265901 feat: scanner unknown value + comparison rescan modes, find bar height fix
Add Cheat Engine-style scan conditions: Unknown Value captures all
aligned addresses as baseline, then Changed/Unchanged/Increased/Decreased
narrow results by comparing current vs previous values. Exact Value
mode unchanged. Also fix find bar search box height to match buttons
and improve MCP bridge instructions.
2026-03-03 11:32:13 -07:00
IChooseYou
86499e58ee fix: remove value history cooldown hack, dismiss popup on clear
The cooldown suppressed tracking for ~1s but the popup persisted showing
stale "1h ago" values because applyDocument skips popup dismissal.
Replaced with explicit dismissHistoryPopup() after clear+refresh so the
popup is gone immediately. Value tracking resumes on the next async cycle
with a clean baseline (m_refreshGen++ discards in-flight reads,
m_prevPages.clear() prevents phantom diffs).
2026-03-03 08:38:08 -07:00
IChooseYou
b2ae8d5a5d fix: insert above node, clear value history cooldown, search context menu
- Insert 4/8 now inserts above the right-clicked node and shifts siblings
  down instead of appending at end. Insert key shortcut (Shift+Ins = 4,
  Ins = 8). Falls back to append when clicking empty space.
- Clear Value History uses a 5-cycle cooldown counter so heat stays gone
  for ~1s instead of returning on the next async refresh.
- Right-click Search defers showFindBar via QTimer::singleShot so focus
  isn't stolen by the closing context menu.
2026-03-03 08:31:49 -07:00
IChooseYou
6768f04e9a Merge pull request #6 from LabGuy94/add-macos-support
Fix file opening on macOS
2026-03-03 08:31:25 -07:00
Lab
c6e5f6508f Fix file opening on macOS 2026-03-02 15:25:57 -08:00
IChooseYou
e6529052b3 fix: clear value history clears subtree, add Copy Line and Search to context menu
- Clear Value History now removes history for all descendant nodes too
- Add "Copy Line" right-click menu item
- Add "Search..." right-click menu item (opens Ctrl+F find bar)
- Move showFindBar() to public in editor.h
2026-03-02 15:34:37 -07:00
IChooseYou
d43e989992 Merge pull request #5 from LabGuy94/add-macos-support
Add macOS support and CI
2026-03-02 14:57:55 -07:00
IChooseYou
879e9f4047 fix: global blue highlight, Ctrl+F find bar with prev/next/close buttons
- Change QPalette::Highlight from theme.selection to theme.hover globally
- RcxEditor find: use SCI_SEARCHINTARGET + INDIC_COMPOSITIONTHICK indicator
  (selection rendering is disabled, so findFirst was invisible)
- Re-apply find indicators after applyDocument() refresh cycle
- Add prev/next/close buttons to find bars in both Reclass and C/C++ modes
- Buttons styled with hover/pressed states matching tab styling
2026-03-02 14:53:14 -07:00
Lab
e0d5a799b4 Add macOS support and CI 2026-03-02 11:34:22 -08:00
IChooseYou
efae193520 feat: value history timestamps, Ctrl+F search, base address fixes
- Add timestamps to ValueHistory ring buffer, expose via new MCP tool
  node.history, show relative time in popup ("26s ago", "2m ago")
- Add "Clear Value History" right-click menu for single and multi-select
- Add Ctrl+F find bar to RcxEditor with live search, Enter-to-next, wrap
- Fix Ctrl+F in workspace dock to auto-focus search field
- Add "Change to float" quick-convert for Hex32 right-click menu
- Sort workspace explorer by children count descending (most fields first)
- Fix provider->base() overwriting saved base address from .rcx files
- Add formula support to MCP change_base operation
- Re-evaluate baseAddressFormula on provider attach in selectSource()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 10:00:17 -07:00
IChooseYou
ba1c2f8e5a refactor: process picker themed styling, context menu, auto-select
Extract shared init into initUi(). Apply dark theme styling from global
palette to table, header, filter, and buttons. Add right-click context
menu with Copy PID/Name/Path. Auto-select last attached process on open.
Remove duplicate attach->accept() connection from .ui (handled in code).
2026-03-02 08:24:39 -07:00
IChooseYou
5a0a4d1802 feat: recent files menu, remove split visibility, clean up demo data
Add Recent Files submenu under File menu (persists last 10 opened/saved
files in QSettings). Hide Remove Split action until a split actually
exists. Remove _SAMPLE_OBJECT demo class from both buildEmptyStruct and
buildEditorDemo. Create a second empty class tab on selfTest so the user
starts with a clean workspace.
2026-03-02 07:50:46 -07:00
Sen66
030eb34510 fix: include shim also on linux 2026-03-02 00:11:37 +01:00
Sen66
2939b25895 fix: build instructions for fadec on cmake build 2026-03-02 00:08:11 +01:00
Sen66
d38cb02fa2 fix: mingw build 2026-03-01 23:58:06 +01:00
IChooseYOu
9f285b37b2 Merge remote-tracking branch 'origin/msvc' 2026-03-01 14:03:34 -07:00
IChooseYOu
cae599a0c6 fix: fixed-width float formatting, fix test_32bit_support on Linux CI
Float values now use a fixed 7-char body (digits.decimals + f suffix)
that adapts decimal places to the integer magnitude. Removes the
variable-width 'g' format and sign-space prefix.

Set QT_QPA_PLATFORM=offscreen for test_32bit_support so it no longer
crashes on headless Linux CI without an X display.
2026-03-01 14:02:40 -07:00
Sen66
d0734ba8be update readme with MSVC/VS guide 2026-03-01 22:02:07 +01:00
Sen66
696ff044ac msvc VS22+ support 2026-03-01 21:54:17 +01:00
Sen66
da312ccac6 fix app close crash, fix error on msvc 2026-03-01 21:49:49 +01:00
Sen66
552b45b16c Fix QSci assert on msvc with text being nullptr 2026-03-01 21:40:57 +01:00
Sen66
e89fd4a6c1 make fadec, raw_pdb submodule 2026-03-01 18:55:26 +01:00
Sen66
7524004b32 remove /fadec and /raw_pdb content 2026-03-01 18:46:01 +01:00
IChooseYou
ed8a44917b feat: 32-bit process support, scanner rescan filtering, suppress flash on navigate
- Add pointerSize() to Provider base; WoW64/ELF detection in ProcessMemory,
  WinDbg, and RemoteProcessMemory plugins
- Wire pointer size through NodeTree, source/XML imports, C++ generator,
  controller, compose, address parser, and RPC protocol header
- Add is32Bit to PluginProcessInfo and ProcessInfo; show (32-bit) in picker
- Scanner rescan now filters results against the current input value
- Go-to-address from scanner resets change tracking to prevent false flashing
2026-03-01 07:42:40 -07:00
IChooseYou
ecfac3decf fix: add missing test source files to repository 2026-02-28 12:54:38 -07:00
IChooseYou
851d744263 fix: rescan performance overhaul, background thread, WinDbg regions
Move rescan to background thread via ScanEngine::startRescan() to
prevent UI freeze. Fix populateTable bottleneck caused by
QHeaderView::ResizeToContents iterating all rows (6s -> 0ms for 512
results). Add chunked batch reads (256KB spans), enumerateRegions()
for WinDbg/ProcessMemory providers, cancel support, and diagnostic
logging throughout the scanner pipeline.
2026-02-28 12:53:25 -07:00
IChooseYou
41e2f9f662 feat: scanner panel with signature/value search, rescan, address delegate
- Signature mode (IDA-style patterns with wildcards) and value mode (typed exact match)
- Async scan engine with progress, cancel support
- Re-scan updates all results with unified progress (single-pass read + table build)
- Previous value column appears after first re-scan
- WinDbg backtick address format with dimmed leading zeros (AddressDelegate)
- Inline editing: address expressions navigate, value edits write to provider
- Right-click context menu: Copy Address, Copy Value, Go to Address
- Auto-sized columns, themed buttons with icons, dynamic combo width
- 49 UI tests covering scan, rescan, editing, theming, progress completion
2026-02-28 11:53:51 -07:00
IChooseYou
95faf027a9 refactor: rename helpers to static fields, block-style rendering, sibling insert
Rename isHelper/ToggleHelper to isStatic/ToggleStatic across core, compose,
controller, editor, and generator. Static fields now render with block syntax
(static Type name { return expr } → 0xADDR) and support collapsed/expanded
display. Add "Add Static Field" context menu for sibling nodes. Update
expression span parser, completions, C++ generator comments, and all tests.
2026-02-28 08:21:00 -07:00
IChooseYou
6a51c904de feat: type selector overhaul, fuzzy search, address parser, value tracking
Redesign type selector popup with fuzzy subsequence matching, per-category
icons, field summary tooltips, compact chips, and pointer target primitives.
Add address expression parser with arithmetic and register support.
Enable track value changes by default.
2026-02-28 06:59:22 -07:00
IChooseYou
0d73575ea7 fix: C++ generator bitfields, sizeof placement, Ctrl+F search, view sync
- Generator emits proper bitfield members instead of padding stubs
- Named bitfield structs (MitigationFlagsValues etc) now converted by parser
- sizeof comment moved from top to closing brace (}; // sizeof 0x80)
- C/C++ view syncs with workspace double-click and controller navigation
- Ctrl+F incremental search in C++ code view (Enter=next, Escape=close)
- Workspace dock resizable via 1px drag handle separator
- Regenerated Vergilius_25H2.rcx with all fixes (61 named bitfield containers)
2026-02-26 12:07:55 -07:00
IChooseYou
aa04cfcb5c feat: add Vergilius-to-RCX converter, full Windows 11 25H2 kernel structs
Add tools/vergilius_to_rcx.py: scrapes struct definitions from
vergiliusproject.com and generates .rcx JSON files. Supports bitfields,
arrays, self-referential pointers, deep union/struct nesting, and
cross-struct references. Offsets correctly stored as parent-relative.

Add src/examples/Vergilius_25H2.rcx: 1,690 kernel structs (18,924 nodes)
from Windows 11 25H2 including _EPROCESS, _KTHREAD, _MMPFN, _PEB, etc.

Remove orange M_CYCLE background on self-referential pointer children —
rows now render with normal theme background while retaining click-to-
materialize behavior.
2026-02-26 11:02:12 -07:00
IChooseYou
1465e7fbed feat: Vergilius-style C++ generator, struct type click fix, item view highlight fix
Rewrite C++ generator for Vergilius-style output: inline anonymous
structs/unions, reference opaque types by name with struct keyword
prefix, size comments, aligned offset comments, no anon_ stubs.

Fix struct type name not clickable in editor headers (headerTypeNameSpan
assumed "struct TYPENAME" format but named structs use bare name).

Add static_assert toggle in Options > Generator, default off.

Fix item view highlight bleed: patch PE_PanelItemViewRow to use
theme.hover so row background matches CE_ItemViewItem.
2026-02-26 08:21:15 -07:00
IChooseYou
52f751e751 fix: redesign Type Aliases dialog — visible presets, compact layout
stdint button now fills cells with actual type names instead of clearing
to empty. Removed redundant Reset button, hidden column/row headers,
filtered out irrelevant types (Vec/Mat/Struct/Array). Fixed item view
hover being invisible on dark themes by painting explicit fillRect.
2026-02-25 17:39:17 -07:00
IChooseYou
0a19789a9d feat: enhance workspace dock, reorganize menus, fix Reclass Dark theme
- Workspace dock: show member count per type, expandable child rows
  (Type Name format, Hex padding filtered), search/filter box with
  recursive matching, collapsed by default, double-click navigates
  to member in editor
- Menu reorganization: Import/Export submenus, new Tools menu (Type
  Aliases, MCP Server, Options), Data Source moved to View, renamed
  Unload→Close Project, Unsplit→Remove Split, Current Tab Source→
  Data Source
- View menu: add Relative Offsets toggle (persisted, applies to all
  editors and new splits)
- Fix Reclass Dark theme: hover/selected colors were identical to
  background (#1e1e1e), now #2a2a2a/#2a2d2e for visible contrast
- Dim MDI tab text via QPalette::WindowText (Fusion ignores CSS color)
- Remove dead QProxyStyle tab handlers (never called for QMdiArea)
2026-02-25 14:27:02 -07:00
IChooseYou
62a68bef80 fix: align workspace dock header with MDI tab bar, dim tab text
Use QProxyStyle for tab height (24px) and text color instead of CSS.
Selected/hover tabs now use textDim to match the dock header.
2026-02-24 15:16:33 -07:00
IChooseYou
4941f860b6 docs: fix misleading README claims, add missing features, remove hr noise
- Fix "server does not start by default" (MCP now auto-starts)
- Rephrase tagline to name ReClass.NET/ReClassEx directly
- Add missing features: enums, bitfields, PDB import, themes, disasm preview, heatmap, MDI tabs, import/export
- Note Qt 5 support alongside Qt 6
- Align autoStartMcp default to true in options dialog
- Remove all horizontal rule separators
2026-02-24 12:48:50 -07:00
IChooseYou
c45d51d736 feat: shimmer status bar for MCP activity, auto-start MCP, remove "Ready" spam
- Add ShimmerLabel widget with animated glow band for MCP tool activity
- Separate app/MCP status channels (setAppStatus/setMcpStatus/clearMcpStatus)
- 750ms delayed clear so shimmer stays visible after fast tool calls
- MCP auto-starts on launch by default
- Remove "Ready" text that was overwriting useful status info
- Add statusText field to project.state MCP response
2026-02-24 12:31:25 -07:00
IChooseYou
5b46065403 feat: enum/bitfield editing, MCP guard rails, PDB anonymous type inlining
- Enum inline editing: name/value commit handling, auto-sort by value
- Bitfield support in PDB import with proper container nodes
- Per-member hover/selection highlighting (kMemberBit encoding)
- Context menu fixes for enum/bitfield member lines
- MCP pagination (limit/offset), includeMembers param, tree.search tool
- MCP status bar activity indicator for tool calls
- PDB anonymous type inlining: inline <unnamed-tag> types as children
- Skip anonymous pointer targets to prevent root orphans
- Enum import diagnostics for debugging missing enums
2026-02-24 10:37:42 -07:00
IChooseYou
4706f7b782 Merge branch 'docs' — update README and add banner SVGs 2026-02-23 18:33:41 -07:00
IChooseYou
fe9bfafa3b Merge pull request #3 from H4vC/main
perf: removed redundant cache invalidations and preindexed lookups for pdbs
2026-02-23 16:07:27 -07:00
IChooseYou
ff928df685 feat: enum support, workspace styling, EPROCESS/MMPFN test data
- Import enums from C/C++ source and PDB with name/value members
- Compose/format/generate enum definitions properly
- Workspace dock: rename to Project, theme-based titlebar and selection
- Add comprehensive EPROCESS.rcx (325 nodes) and MMPFN.rcx (65 nodes)
2026-02-23 16:01:35 -07:00
Brit
d6e3c182fc perf(import-compose): removed redundant cache invalidations and preindexed lookups 2026-02-23 17:56:44 +01:00
IChooseYou
078a6028f0 fix: WinDbg provider stops auto-selecting module, new tabs inherit source
- WinDbg provider no longer picks arbitrary module[0] as name/base
  (was showing "WS2_32" for kernel dumps). Name is now generic
  "WinDbg (Live)" / "WinDbg (Dump)", base stays 0 so controller
  doesn't override user's address.
- Added throttled read failure logging to WinDbg provider.
- New tabs (File→New Class, workspace right-click) inherit the
  current tab's source/provider so users don't have to re-attach.
- Updated WinDbg provider tests for new behavior.
2026-02-23 08:08:46 -07:00
Sen66
d7a6e1862e update height of banner 2026-02-22 22:02:27 +01:00
Sen66
1ddf47a754 update svg 2026-02-22 22:01:29 +01:00
Sen66
1a885a8b1d update readme 2026-02-22 21:54:11 +01:00
IChooseYou
67218d3e48 fix: move payload init out of DllMain to avoid loader lock deadlock
RcxPayloadInit() is now an exported function called after LoadLibrary
returns. DllMain only handles cleanup on detach. Timer queue creation
under the loader lock was crashing target processes.
2026-02-22 13:14:01 -07:00
IChooseYou
f651edd740 feat: remove nonce/bootstrap from remote process IPC, use PID-only naming
Shared memory names simplified to Local\RCX_SHM_<pid>, no bootstrap
handshake needed. Payload uses CreateTimerQueueTimer (10ms poll) instead
of a dedicated server thread.
2026-02-22 11:36:24 -07:00
IChooseYou
25aaace382 Merge remote-tracking branch 'origin/fix-issue-2' 2026-02-22 11:09:05 -07:00
Sen66
b5ddb042b8 Try to fix missing DLLs at CI windows builds
Fix https://github.com/IChooseYou/Reclass/issues/2
2026-02-22 19:06:50 +01:00
IChooseYou
e900dea836 fix: menu bar item paint no longer covers title bar bottom border
Take full ownership of CE_MenuBarItem in MenuBarStyle — never
delegate to Fusion which unconditionally fills the full item rect.
Non-hovered items draw text only (transparent bg lets parent border
show through). Hover/pressed states fill adjusted rect leaving 1px
for the border. Pressed state uses darker(130) for visual feedback.
2026-02-22 11:05:54 -07:00
IChooseYou
b647a334bc docs: fix Remote Process description 2026-02-22 09:14:04 -07:00
IChooseYou
fc390bc1f7 docs: add Remote Process data source to README 2026-02-22 09:06:32 -07:00
IChooseYou
7efe740ec1 fix: hover invisible when theme.hover == background, remove CSS on QMenuBar
Move hover color fixup into Theme::fromJson so all consumers get a
visible hover automatically. Remove duplicate lighter(130) fallback
from applyGlobalTheme. Replace QMenuBar CSS with QPalette so
MenuBarStyle QProxyStyle is not bypassed. Add PE_PanelMenuBar and
CE_MenuBarEmptyArea suppression so Fusion never paints over the
title bar background.
2026-02-22 08:58:57 -07:00
IChooseYou
48409d1d38 fix: guard __cdecl __debugbreak behind PDB_COMPILER_MSVC for Linux build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:33:36 -07:00
IChooseYou
df1435d9b7 Merge remote-tracking branch 'origin/refactor-readme' 2026-02-22 07:30:00 -07:00
IChooseYou
5e11ff5496 feat: Remote Process Memory plugin, source menu icons, base address fix
- Remote Process Memory plugin: shared-memory IPC payload injected into
  target process (CreateRemoteThread on Win, ptrace+dlopen on Linux),
  VirtualQuery-based memory safety, PEB-based image base, batch reads
- Source dropdown: SVG icons per provider type, DLL filename shown
- Fix base address not updating when switching to a new source provider
- ProviderRegistry carries DLL filename from PluginManager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:29:56 -07:00
Sen66
22842d9801 I'm too tired 2026-02-22 14:22:36 +01:00
Sen66
50acde60cb refactor readme 2026-02-22 14:21:12 +01:00
IChooseYou
1d7d384b93 feat: PDB import via RawPDB, no msdia140.dll dependency
Replace DIA SDK COM-based PDB importer with RawPDB (MolecularMatters)
which reads PDB files directly via memory-mapped I/O. Adds File menu
"Import PDB..." dialog with type filtering, selection, and progress.

- Vendor raw_pdb into third_party/
- Two-phase API: enumeratePdbTypes() + importPdbSelected()
- Full recursive import of structs/unions/arrays/pointers/bitfields
- PDB import dialog with name filter, select-all, type count
- Benchmark: 1654 types from ntkrnlmp.pdb in 16ms
- Reorganize import/export files into src/imports/
2026-02-21 17:18:24 -07:00
IChooseYou
3a76b03c85 fix: continuous top border on status bar tabs, baseline alignment, 15% taller
- ViewTabButton always paints 1px top border matching status bar hairline;
  selected tab's accent line paints over it
- Remove SegmentedContainer (caused gap on unselected tab)
- Shared baseline alignment between tab text and status label
- Status bar height * 1.15
2026-02-21 11:41:46 -07:00
IChooseYou
ac94855d6c feat: status bar visual upgrade, unified release job
Status bar: top hairline separator, vertical divider between toggle
and status text, segmented-control container with border/separators
around view buttons, accent line 2->3px, proper sizeHint with
breathing room, default system font instead of monospace override.

CI: replace per-job release uploads with a single release job that
waits for both windows and linux, then publishes both artifacts to
one GitHub release.
2026-02-21 11:09:28 -07:00
IChooseYou
d65b6c5a29 feat: address expression parser with module resolution and pointer deref
Merge branch 'address-parser'. Adds AddressParser supporting:
- Hex arithmetic with +-*/ and operator precedence
- Module base resolution via <Module.exe> syntax
- Pointer dereference via [addr] syntax with nesting
- WinDbg backtick-separated addresses (7ff6`6cce0000)
- Formula persistence in project files and source switching
2026-02-21 09:12:11 -07:00
IChooseYou
d45ee9e4c9 ci: install Qt-matching MinGW 13.1.0 to fix test segfaults
System MinGW on windows-latest is GCC 15.2 which has ABI mismatch
with Qt 6.8.1 (built with MinGW 13.1.0), causing all tests to
segfault. Install the matching toolchain via aqtinstall tools and
use it instead of the system compiler.
2026-02-21 09:07:09 -07:00
Sen66
31115014a5 ignore some more build directories 2026-02-21 17:04:59 +01:00
Sen66
8e88d588be Add AddressParser + tests, remove symbol from commandrow 2026-02-21 17:03:44 +01:00
IChooseYou
b089e20d36 ci: retrigger build 2026-02-20 17:01:57 -07:00
IChooseYou
5fa1dd0ab4 fix: add missing header declarations and editor scroll fixes
- mainwindow.h: add m_viewBtnGroup, m_btnReclass, m_btnRendered members,
  syncViewButtons() declaration, QButtonGroup/QPushButton includes,
  remove applyTabWidgetStyle() declaration
- editor.cpp: reset xOffset on applyDocument, clamp in restoreViewState
- test_editor.cpp: add horizontal scroll reset test
2026-02-20 13:22:23 -07:00
IChooseYou
3b1fe7ff35 fix: use findChild<QWidget*> for ResizeGrip to fix GCC 15 static_assert
GCC 15.2 on CI enforces Q_OBJECT requirement for findChild template
parameter. ResizeGrip is a local class without Q_OBJECT, so use
QWidget* with static_cast instead.
2026-02-20 13:18:03 -07:00
IChooseYou
4595b366e3 ci: use system MinGW from runner, drop tools_mingw1310 2026-02-20 13:05:13 -07:00
IChooseYou
33d7dc74cb ci: switch Windows CI from MSVC to MinGW, run Linux in parallel 2026-02-20 12:57:51 -07:00
IChooseYou
e118231bb1 docs: add screenshots to README 2026-02-20 12:32:04 -07:00
IChooseYou
0cfd7ad87a feat: sort primitives alphabetically in type chooser 2026-02-20 07:37:32 -07:00
IChooseYou
2d3ce63b54 ci: disable UI tests in CI, delete test_com_security
CI now passes -DBUILD_UI_TESTS=OFF so only headless tests
(core, format, compose, provider, command_row, generator,
import_xml, import_source, export_xml, disasm) build and run.

Removed xvfb-run and exclude-regex hacks from both Windows
and Linux CI — the CMake option handles it cleanly.

Deleted test_com_security (windbg-only, not needed in CI).
2026-02-20 07:27:23 -07:00
IChooseYou
0e087fa3a4 feat: primitive pointer modifiers, type chooser fixes, double-click to edit
Type chooser:
- Fix PointerTarget mode hiding primitives due to stale modifier state
- Preselect */[n] modifier buttons to reflect current node type
- Primitive pointer support: int32*, double**, etc with provider deref
- hex64*/ptr64* with * modifier falls back to void* (meaningless deref)
- isValidPrimitivePtrTarget guard in controller, compose, format
- Modifier toggle no longer resets list selection
- Primitive pointers open FieldType mode (not PointerTarget)
- Type edit requires double-click (was single-click, too easy to misclick)

Other:
- Custom dock titlebar with themed close button, no float button
- Status bar font synced at startup
- Resize grip reworked as direct MainWindow child, font-independent
- File menu "Source" renamed to "Current Tab Source"

Tests: 41 type_selector, 39 editor, 17 controller (200 total, 0 failures)
2026-02-20 07:21:02 -07:00
IChooseYou
c7afe363f3 feat: custom dock titlebar, resize grip symmetry fix, status bar font sync
- Replace default dock widget titlebar with custom label + themed ✕ close button
- Remove float/popout button from project tree dock
- Fix resize grip corner symmetry (bottom margin 4→0)
- Sync editor font to status bar and dock titlebar at startup
- Add testResizeGripCornerSymmetry test
2026-02-19 18:10:52 -07:00
IChooseYou
2a44d2ac57 fix: narrow inline editor selection for pointer values, resolve correct write address
resolvedSpanFor() now applies narrowPtrValueSpan() to trim the "// Module+offset"
symbol comment from the editable span, matching hitTestTarget(). Previously the
full value column text was selected, making the parser fail on commit (toULongLong
rejected the non-hex suffix), so pointer value saves were silently no-ops.

With the parse now succeeding, a second bug was exposed: setNodeValue() computed
write addresses via computeOffset() which sums tree offsets without dereferencing
pointers. For nodes inside expanded pointer targets (e.g. VTable entries), this
wrote to struct_base+child_offset instead of *ptr_value+child_offset, causing an
access violation crash. The fix passes lm->offsetAddr (the compose-resolved
absolute address) through the inlineEditCommitted signal so setNodeValue() uses
the correct dereferenced address.
2026-02-19 13:05:25 -07:00
IChooseYou
d989e2a947 feat: safe workspace tree deletion with reference cleanup and confirmation
- Add deleteRootStruct() that clears orphaned refId references before removal
- Show confirmation dialog listing all fields that reference the deleted type
- Auto-switch view to next root struct when the viewed one is deleted
- Entire operation is a single undo macro (Ctrl+Z restores everything)
2026-02-19 10:06:13 -07:00
IChooseYou
7678da033d feat: source management, cross-tab type visibility, default VS2022 theme
- Add clearSources() and File→Source submenu for provider management
- Fix type picker not showing newly created structs (empty structTypeName)
- Add cross-tab type visibility via shared project document list
- Import external types into local document on selection
- Default theme to VS2022 on first launch
- Add test_source_management and test_type_visibility test suites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:29:18 -07:00
IChooseYou
acc3ebf5db feat: track value changes toggle, hover scroll fix, ptr* convert, hex split 2026-02-19 06:32:58 -07:00
IChooseYou
26217f5de8 feat: switch provider addressing from RVA to absolute, add pointer expansion tests 2026-02-18 13:07:48 -07:00
IChooseYou
fa0d9a377b fix: type chooser updates colors when theme changes
Add applyTheme() to TypeSelectorPopup that refreshes palette and
stylesheets for all child widgets. Controller connects it to
ThemeManager::themeChanged on popup creation.
2026-02-18 09:59:50 -07:00
IChooseYou
b1d3e52204 fix: type chooser SVG icons and gutter scale with editor zoom level
Derive icon size, gutter width, and icon column width from font
metrics instead of hardcoded 16/10/20 pixel values. Popup width
calculation also scales with font.
2026-02-18 09:47:25 -07:00
193 changed files with 97970 additions and 20705 deletions

View File

@@ -2,7 +2,8 @@ name: Build
on:
push:
branches: [main]
branches:
- "**"
pull_request:
branches: [main]
@@ -18,55 +19,68 @@ jobs:
with:
submodules: recursive
- name: Install Qt6
- name: Install Qt6 and MinGW
uses: jurplel/install-qt-action@v4
with:
version: '6.8.1'
arch: 'win64_msvc2022_64'
version: "6.8.1"
arch: "win64_mingw"
tools: "tools_mingw1310,qt.tools.win64_mingw1310"
cache: true
aqtversion: '==3.1.21'
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build
- name: Test
run: ctest --test-dir build --output-on-failure --exclude-regex "test_editor|test_controller|test_windbg_provider|test_com_security"
- name: Upload artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: Reclass-win64-qt6
path: |
build/Reclass.exe
build/ReclassMcpBridge.exe
build/Plugins/*.dll
build/*.dll
build/platforms/
build/styles/
build/imageformats/
build/iconengines/
build/themes/
build/examples/
build/screenshot.png
- name: Get date tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: date
shell: bash
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
- name: Package release zip
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
shell: bash
run: |
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
gcc --version
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF \
-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
- name: Build
shell: bash
run: |
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
cmake --build build
- name: Install WDK NuGet
shell: pwsh
run: |
nuget install Microsoft.Windows.WDK.x64 -OutputDirectory wdk_pkg
$ntddk = Get-ChildItem wdk_pkg -Recurse -Filter "ntddk.h" |
Where-Object { $_.DirectoryName -like "*km*" } |
Select-Object -First 1
if (!$ntddk) { throw "ntddk.h not found in WDK NuGet package" }
$kmDir = $ntddk.DirectoryName
$incRoot = Split-Path $kmDir -Parent
Write-Host "WDK include root: $incRoot"
echo "WDK_INC_ROOT=$incRoot" >> $env:GITHUB_ENV
$ntos = Get-ChildItem wdk_pkg -Recurse -Filter "ntoskrnl.lib" |
Where-Object { $_.DirectoryName -like "*x64*" } |
Select-Object -First 1
if (!$ntos) { throw "ntoskrnl.lib not found in WDK NuGet package" }
$libRoot = Split-Path (Split-Path $ntos.DirectoryName -Parent) -Parent
Write-Host "WDK lib root: $libRoot"
echo "WDK_LIB_ROOT=$libRoot" >> $env:GITHUB_ENV
$specstr = Get-ChildItem wdk_pkg -Recurse -Filter "specstrings.h" |
Select-Object -First 1
if (!$specstr) { throw "specstrings.h not found in SDK NuGet package" }
$sdkIncRoot = Split-Path $specstr.DirectoryName -Parent
Write-Host "SDK include root: $sdkIncRoot"
echo "SDK_INC_ROOT=$sdkIncRoot" >> $env:GITHUB_ENV
- name: Build kernel driver
shell: cmd
run: call plugins\KernelMemory\driver\build_driver.bat
- name: Test
shell: bash
run: |
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
ctest --test-dir build --output-on-failure
- name: Package release zip
shell: bash
run: |
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
mkdir -p release
cp build/Reclass.exe release/
cp build/ReclassMcpBridge.exe release/
@@ -75,29 +89,22 @@ jobs:
cp -r build/styles release/ 2>/dev/null || true
cp -r build/imageformats release/ 2>/dev/null || true
cp -r build/iconengines release/ 2>/dev/null || true
windeployqt --no-translations --no-system-d3d-compiler --no-opengl-sw release/Reclass.exe
mkdir -p release/Plugins
cp build/Plugins/*.dll release/Plugins/ 2>/dev/null || true
cp plugins/KernelMemory/driver/build/rcxdrv.sys release/Plugins/ 2>/dev/null || true
cp -r build/themes release/ 2>/dev/null || true
cp -r build/examples release/ 2>/dev/null || true
cp build/screenshot.png release/ 2>/dev/null || true
cd release && 7z a ../Reclass-win64-qt6.zip *
- name: Upload release asset
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v2
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
tag_name: snapshot-${{ steps.date.outputs.tag }}
name: Snapshot ${{ steps.date.outputs.tag }}
body: |
Automated snapshot from main branch.
Commit: ${{ github.sha }}
prerelease: false
files: Reclass-win64-qt6.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Reclass-win64-qt6
path: Reclass-win64-qt6.zip
linux:
needs: windows
runs-on: ubuntu-22.04
steps:
@@ -108,9 +115,8 @@ jobs:
- name: Install Qt6
uses: jurplel/install-qt-action@v4
with:
version: '6.8.1'
version: "6.8.1"
cache: true
aqtversion: '==3.1.21'
- name: Install dependencies
run: |
@@ -118,15 +124,13 @@ jobs:
sudo apt-get install -y ninja-build libgl1-mesa-dev libfuse2 libxcb-cursor0
- name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF
- name: Build
run: cmake --build build
- name: Test
run: xvfb-run ctest --test-dir build --output-on-failure --exclude-regex "test_editor|test_controller"
env:
QT_QPA_PLATFORM: offscreen
run: ctest --test-dir build --output-on-failure
- name: Create AppImage
run: |
@@ -164,19 +168,83 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: Reclass-linux64-qt6
path: Reclass-linux64-qt6.AppImage
macos:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
qt_arch: clang_arm64
artifact_name: Reclass-macos-arm64-qt6
zip_name: Reclass-macos-arm64-qt6.zip
- os: macos-15-intel
qt_arch: clang_64
artifact_name: Reclass-macos-x86_64-qt6
zip_name: Reclass-macos-x86_64-qt6.zip
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
brew update
brew install cmake ninja qt
- name: Configure Qt paths
run: |
QT_PREFIX="$(brew --prefix qt)"
echo "QT_PREFIX=$QT_PREFIX" >> "$GITHUB_ENV"
echo "PATH=$QT_PREFIX/bin:$PATH" >> "$GITHUB_ENV"
- name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF -DCMAKE_PREFIX_PATH="$QT_PREFIX"
- name: Build
run: cmake --build build
- name: Test
run: ctest --test-dir build --output-on-failure
- name: Package app zip
run: |
MACDEPLOYQT_BIN="$QT_PREFIX/bin/macdeployqt"
if [ ! -x "$MACDEPLOYQT_BIN" ]; then
MACDEPLOYQT_BIN=$(which macdeployqt 2>/dev/null || find "$RUNNER_WORKSPACE" -name macdeployqt -path "*/bin/*" | head -1)
fi
echo "Found macdeployqt at: $MACDEPLOYQT_BIN"
"$MACDEPLOYQT_BIN" build/Reclass.app -always-overwrite
codesign --force --deep --sign - build/Reclass.app
ditto -c -k --sequesterRsrc --keepParent build/Reclass.app "${{ matrix.zip_name }}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.zip_name }}
release:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [windows, linux, macos]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Get date tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: date
shell: bash
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
- name: Upload release asset
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- name: Create release
uses: softprops/action-gh-release@v2
with:
tag_name: snapshot-${{ steps.date.outputs.tag }}
@@ -185,7 +253,10 @@ jobs:
Automated snapshot from main branch.
Commit: ${{ github.sha }}
prerelease: false
files: Reclass-linux64-qt6.AppImage
files: |
artifacts/Reclass-win64-qt6/Reclass-win64-qt6.zip
artifacts/Reclass-linux64-qt6/Reclass-linux64-qt6.AppImage
artifacts/Reclass-macos-arm64-qt6/Reclass-macos-arm64-qt6.zip
artifacts/Reclass-macos-x86_64-qt6/Reclass-macos-x86_64-qt6.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored
View File

@@ -11,3 +11,7 @@ build/
*.suo
.vs/
CMakeUserPresets.json
plugins/RcNetPluginCompatLayer/bridge/obj
plugins/RcNetPluginCompatLayer/bridge/bin
.cache
*.DS_Store

6
.gitmodules vendored
View File

@@ -1,3 +1,9 @@
[submodule "third_party/qscintilla"]
path = third_party/qscintilla
url = https://github.com/brCreate/QScintilla.git
[submodule "third_party/raw_pdb"]
path = third_party/raw_pdb
url = https://github.com/MolecularMatters/raw_pdb.git
[submodule "third_party/fadec"]
path = third_party/fadec
url = https://github.com/aengelke/fadec.git

View File

@@ -22,6 +22,32 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${_QT_COMPONENTS})
set(QT Qt${QT_VERSION_MAJOR})
message(STATUS "Using ${QT}: ${${QT}_DIR}")
# ── ABI sanity check: prevent MSVC ↔ MinGW Qt mismatch ──
# Building with MSVC against MinGW Qt (or vice versa) compiles fine but
# crashes immediately at runtime (ABI mismatch in QString/QSettings internals).
if(MSVC AND "${${QT}_DIR}" MATCHES "mingw")
message(FATAL_ERROR
"Qt installation was built with MinGW but this project is being compiled with MSVC.\n"
" Qt found at: ${${QT}_DIR}\n"
"This will compile but crash at startup due to ABI mismatch.\n"
"Fix: install Qt for MSVC (e.g. msvc2019_64) and set CMAKE_PREFIX_PATH to it:\n"
" cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.5.2/msvc2019_64 ..")
elseif(MINGW AND "${${QT}_DIR}" MATCHES "msvc")
message(FATAL_ERROR
"Qt installation was built with MSVC but this project is being compiled with MinGW.\n"
" Qt found at: ${${QT}_DIR}\n"
"This will compile but crash at startup due to ABI mismatch.\n"
"Fix: install Qt for MinGW and set CMAKE_PREFIX_PATH to it:\n"
" cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.5.2/mingw_64 ..")
endif()
# ── MSVC compile flags ──
if(MSVC)
# /utf-8: treat source and execution character sets as UTF-8
# /MP: multi-processor compilation
add_compile_options(/utf-8 /MP)
endif()
# Qt5 on Windows needs WinExtras for HICON conversion
set(_QT_WINEXTRAS "")
if(QT_VERSION_MAJOR EQUAL 5 AND WIN32)
@@ -31,6 +57,40 @@ endif()
find_package(QScintilla REQUIRED)
# RawPDB — direct PDB file reader (no DIA SDK / msdia140.dll dependency)
file(GLOB RAW_PDB_SRCS third_party/raw_pdb/src/*.cpp)
add_library(raw_pdb STATIC ${RAW_PDB_SRCS})
target_include_directories(raw_pdb PUBLIC third_party/raw_pdb/src)
target_compile_features(raw_pdb PRIVATE cxx_std_11)
# PDB_CRT.h forward-declares printf/memcmp/etc with __cdecl which conflicts
# with non-MSVC compilers (GCC, Clang, MinGW). Force-include a prefix header
# that pulls in the real CRT headers and strips __cdecl.
if(NOT MSVC)
target_compile_options(raw_pdb PUBLIC
-include "${CMAKE_CURRENT_SOURCE_DIR}/cmake/raw_pdb_prefix.h")
endif()
if(WIN32)
target_link_libraries(raw_pdb PRIVATE rpcrt4)
endif()
# Fadec — generate decode tables (.inc files) from instrs.txt at configure time
find_package(Python3 3.9 REQUIRED)
set(FADEC_DIR "${CMAKE_SOURCE_DIR}/third_party/fadec")
if(NOT EXISTS "${FADEC_DIR}/fadec-decode-public.inc")
message(STATUS "Generating fadec decode tables...")
execute_process(
COMMAND ${Python3_EXECUTABLE} "${FADEC_DIR}/parseinstrs.py" decode
"${FADEC_DIR}/instrs.txt"
"${FADEC_DIR}/fadec-decode-public.inc"
"${FADEC_DIR}/fadec-decode-private.inc"
--32 --64
RESULT_VARIABLE _fadec_result
)
if(NOT _fadec_result EQUAL 0)
message(FATAL_ERROR "Failed to generate fadec decode tables")
endif()
endif()
add_executable(Reclass
src/main.cpp
src/editor.h
@@ -60,19 +120,39 @@ add_executable(Reclass
src/themes/thememanager.cpp
src/themes/themeeditor.h
src/themes/themeeditor.cpp
src/import_reclass_xml.h
src/import_reclass_xml.cpp
src/import_source.h
src/import_source.cpp
src/export_reclass_xml.h
src/export_reclass_xml.cpp
src/imports/import_reclass_xml.h
src/imports/import_reclass_xml.cpp
src/imports/import_source.h
src/imports/import_source.cpp
src/imports/export_reclass_xml.h
src/imports/export_reclass_xml.cpp
src/imports/import_pdb.h
src/imports/import_pdb.cpp
src/imports/import_pdb_dialog.h
src/imports/import_pdb_dialog.cpp
src/scanner.h
src/scanner.cpp
src/scannerpanel.h
src/scannerpanel.cpp
src/mainwindow.h
src/startpage.h
src/dock_tab_buttons.h
src/optionsdialog.h
src/optionsdialog.cpp
src/titlebar.h
src/titlebar.cpp
src/macos_titlebar.h
$<$<PLATFORM_ID:Darwin>:src/macos_titlebar.mm>
src/mcp/mcp_bridge.h
src/mcp/mcp_bridge.cpp
src/addressparser.h
src/addressparser.cpp
src/symbolstore.h
src/symbolstore.cpp
src/symbol_downloader.h
src/symbol_downloader.cpp
src/imports/pe_debug_info.h
src/imports/pe_debug_info.cpp
src/disasm.h
src/disasm.cpp
third_party/fadec/decode.c
@@ -80,6 +160,16 @@ add_executable(Reclass
$<$<PLATFORM_ID:Windows>:src/app.rc>
)
if(APPLE)
set_target_properties(Reclass PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_ICON_FILE "class.icns"
)
target_sources(Reclass PRIVATE src/icons/class.icns)
set_source_files_properties(src/icons/class.icns
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
endif()
target_include_directories(Reclass PRIVATE src third_party/fadec)
target_link_libraries(Reclass PRIVATE
@@ -92,28 +182,99 @@ target_link_libraries(Reclass PRIVATE
${_QT_WINEXTRAS}
)
if(WIN32)
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi)
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi raw_pdb)
# Copy Debugging Tools dbghelp.dll next to Reclass.exe so the Windows
# loader picks it up (app dir > System32). The system dbghelp.dll
# lacks StackWalk2 which the tools dbgeng.dll needs for remote debug.
set(_DBG_TOOLS_DIRS
"C:/Program Files (x86)/Windows Kits/10/Debuggers/x64"
"C:/Program Files/Windows Kits/10/Debuggers/x64")
foreach(_dir ${_DBG_TOOLS_DIRS})
if(EXISTS "${_dir}/dbghelp.dll")
foreach(_dll dbghelp.dll dbgcore.dll symsrv.dll)
if(EXISTS "${_dir}/${_dll}")
add_custom_command(TARGET Reclass POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${_dir}/${_dll}" $<TARGET_FILE_DIR:Reclass>
COMMENT "Copying ${_dll} from Debugging Tools")
endif()
endforeach()
break()
endif()
endforeach()
endif()
add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp)
target_link_libraries(ReclassMcpBridge PRIVATE ${QT}::Core ${QT}::Network)
if(APPLE)
add_custom_command(TARGET ReclassMcpBridge POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:ReclassMcpBridge>
$<TARGET_FILE_DIR:Reclass>/ReclassMcpBridge
COMMENT "Bundling ReclassMcpBridge into Reclass.app"
)
endif()
# Copy built-in theme JSON files to build directory
# Copy built-in theme JSON files next to the executable.
# For single-config generators (Ninja/Make) the exe is in ${CMAKE_BINARY_DIR},
# for multi-config generators (MSVC/Xcode) it's in ${CMAKE_BINARY_DIR}/<config>.
# Using a post-build copy with $<TARGET_FILE_DIR:Reclass> handles both.
file(GLOB _theme_files "${CMAKE_SOURCE_DIR}/src/themes/defaults/*.json")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/themes")
# Single-config: configure_file for IDE convenience (available before first build)
if(NOT CMAKE_CONFIGURATION_TYPES)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/themes")
foreach(_tf ${_theme_files})
get_filename_component(_name ${_tf} NAME)
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
endforeach()
endif()
# Post-build: always copy to the actual exe directory (works for all generators)
add_custom_command(TARGET Reclass POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/themes"
COMMENT "Creating themes directory next to executable")
foreach(_tf ${_theme_files})
get_filename_component(_name ${_tf} NAME)
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
add_custom_command(TARGET Reclass POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${_tf}" "$<TARGET_FILE_DIR:Reclass>/themes/${_name}")
endforeach()
# Copy example .rcx files to build directory
if(APPLE)
target_sources(Reclass PRIVATE ${_theme_files})
set_source_files_properties(${_theme_files}
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/themes")
endif()
# Copy example .rcx files next to the executable (same logic as themes)
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
if(NOT CMAKE_CONFIGURATION_TYPES)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
foreach(_ef ${_example_files})
get_filename_component(_name ${_ef} NAME)
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
endforeach()
endif()
add_custom_command(TARGET Reclass POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/examples"
COMMENT "Creating examples directory next to executable")
foreach(_ef ${_example_files})
get_filename_component(_name ${_ef} NAME)
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
add_custom_command(TARGET Reclass POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${_ef}" "$<TARGET_FILE_DIR:Reclass>/examples/${_name}")
endforeach()
if(APPLE)
target_sources(Reclass PRIVATE ${_example_files})
set_source_files_properties(${_example_files}
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/examples")
endif()
include(deploy)
@@ -154,17 +315,22 @@ if(BUILD_TESTING)
# ── Headless tests (Qt::Core only — safe for CI without a display) ──
add_executable(test_core tests/test_core.cpp src/format.cpp src/compose.cpp)
add_executable(test_core tests/test_core.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
target_include_directories(test_core PRIVATE src)
target_link_libraries(test_core PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_core COMMAND test_core)
add_executable(test_format tests/test_format.cpp src/format.cpp)
add_executable(test_typeinfer tests/test_typeinfer.cpp)
target_include_directories(test_typeinfer PRIVATE src)
target_link_libraries(test_typeinfer PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_typeinfer COMMAND test_typeinfer)
add_executable(test_format tests/test_format.cpp src/format.cpp src/addressparser.cpp)
target_include_directories(test_format PRIVATE src)
target_link_libraries(test_format PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_format COMMAND test_format)
add_executable(test_compose tests/test_compose.cpp src/compose.cpp src/format.cpp)
add_executable(test_compose tests/test_compose.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
target_include_directories(test_compose PRIVATE src)
target_link_libraries(test_compose PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_compose COMMAND test_compose)
@@ -180,156 +346,248 @@ if(BUILD_TESTING)
add_test(NAME test_command_row COMMAND test_command_row)
add_executable(test_generator tests/test_generator.cpp
src/generator.cpp src/compose.cpp src/format.cpp)
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
target_include_directories(test_generator PRIVATE src)
target_link_libraries(test_generator PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_generator COMMAND test_generator)
add_executable(test_import_xml tests/test_import_xml.cpp
src/import_reclass_xml.cpp src/format.cpp src/compose.cpp)
src/imports/import_reclass_xml.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
target_include_directories(test_import_xml PRIVATE src)
target_link_libraries(test_import_xml PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_import_xml COMMAND test_import_xml)
add_executable(test_import_source tests/test_import_source.cpp
src/import_source.cpp src/format.cpp src/compose.cpp)
src/imports/import_source.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
target_include_directories(test_import_source PRIVATE src)
target_link_libraries(test_import_source PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_import_source COMMAND test_import_source)
add_executable(test_export_xml tests/test_export_xml.cpp
src/export_reclass_xml.cpp src/import_reclass_xml.cpp src/format.cpp src/compose.cpp)
src/imports/export_reclass_xml.cpp src/imports/import_reclass_xml.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
target_include_directories(test_export_xml PRIVATE src)
target_link_libraries(test_export_xml PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_export_xml COMMAND test_export_xml)
add_executable(test_disasm tests/test_disasm.cpp
src/disasm.cpp src/compose.cpp src/format.cpp
src/disasm.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
third_party/fadec/decode.c third_party/fadec/format.c)
target_include_directories(test_disasm PRIVATE src third_party/fadec)
target_link_libraries(test_disasm PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_disasm COMMAND test_disasm)
add_executable(test_addressparser tests/test_addressparser.cpp src/addressparser.cpp)
target_include_directories(test_addressparser PRIVATE src)
target_link_libraries(test_addressparser PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_addressparser COMMAND test_addressparser)
add_executable(test_static_fields tests/test_static_fields.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
target_include_directories(test_static_fields PRIVATE src)
target_link_libraries(test_static_fields PRIVATE ${QT}::Core ${QT}::Test)
add_test(NAME test_static_fields COMMAND test_static_fields)
add_executable(test_scanner tests/test_scanner.cpp src/scanner.cpp)
target_include_directories(test_scanner PRIVATE src)
target_link_libraries(test_scanner PRIVATE ${QT}::Core ${QT}::Concurrent ${QT}::Test)
add_test(NAME test_scanner COMMAND test_scanner)
add_executable(test_32bit_support tests/test_32bit_support.cpp
src/generator.cpp src/imports/import_source.cpp src/imports/import_reclass_xml.cpp
src/compose.cpp src/format.cpp src/addressparser.cpp)
target_include_directories(test_32bit_support PRIVATE src
${CMAKE_SOURCE_DIR}/plugins/RemoteProcessMemory)
target_link_libraries(test_32bit_support PRIVATE ${QT}::Core ${QT}::Widgets ${QT}::Test)
add_test(NAME test_32bit_support COMMAND test_32bit_support)
set_tests_properties(test_32bit_support PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
if(WIN32)
add_executable(test_import_pdb tests/test_import_pdb.cpp
src/imports/import_pdb.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
target_include_directories(test_import_pdb PRIVATE src)
target_link_libraries(test_import_pdb PRIVATE
${QT}::Core ${QT}::Test raw_pdb)
add_test(NAME test_import_pdb COMMAND test_import_pdb)
add_executable(bench_import_pdb tests/bench_import_pdb.cpp
src/imports/import_pdb.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
target_include_directories(bench_import_pdb PRIVATE src)
target_link_libraries(bench_import_pdb PRIVATE
${QT}::Core ${QT}::Test raw_pdb)
add_test(NAME bench_import_pdb COMMAND bench_import_pdb)
endif()
# ── UI tests (require Qt::Widgets / QScintilla / display — skip on headless CI) ──
option(BUILD_UI_TESTS "Build tests that require a display (Qt Widgets)" ON)
if(BUILD_UI_TESTS)
add_executable(test_controller tests/test_controller.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
add_executable(test_controller tests/test_controller.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_controller PRIVATE src third_party/fadec)
target_link_libraries(test_controller PRIVATE
target_include_directories(test_controller PRIVATE src third_party/fadec)
target_link_libraries(test_controller PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_controller PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_controller COMMAND test_controller)
if(WIN32)
target_link_libraries(test_controller PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_controller COMMAND test_controller)
add_executable(test_validation tests/test_validation.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
add_executable(test_context_menu tests/test_context_menu.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_validation PRIVATE src third_party/fadec)
target_link_libraries(test_validation PRIVATE
target_include_directories(test_context_menu PRIVATE src third_party/fadec)
target_link_libraries(test_context_menu PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_validation PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_validation COMMAND test_validation)
if(WIN32)
target_link_libraries(test_context_menu PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_context_menu COMMAND test_context_menu)
add_executable(test_context_menu tests/test_context_menu.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
add_executable(test_source_management tests/test_source_management.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_context_menu PRIVATE src third_party/fadec)
target_link_libraries(test_context_menu PRIVATE
target_include_directories(test_source_management PRIVATE src third_party/fadec)
target_link_libraries(test_source_management PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_context_menu PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_context_menu COMMAND test_context_menu)
if(WIN32)
target_link_libraries(test_source_management PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_source_management COMMAND test_source_management)
add_executable(test_editor tests/test_editor.cpp
src/editor.cpp src/compose.cpp src/format.cpp
add_executable(test_editor tests/test_editor.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
src/providerregistry.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_editor PRIVATE src third_party/fadec)
target_link_libraries(test_editor PRIVATE
target_include_directories(test_editor PRIVATE src third_party/fadec)
target_link_libraries(test_editor PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
QScintilla::QScintilla)
add_test(NAME test_editor COMMAND test_editor)
add_test(NAME test_editor COMMAND test_editor)
add_executable(test_rendered_view tests/test_rendered_view.cpp
src/generator.cpp src/compose.cpp src/format.cpp)
target_include_directories(test_rendered_view PRIVATE src)
target_link_libraries(test_rendered_view PRIVATE
add_executable(test_rendered_view tests/test_rendered_view.cpp
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
target_include_directories(test_rendered_view PRIVATE src)
target_link_libraries(test_rendered_view PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
QScintilla::QScintilla)
add_test(NAME test_rendered_view COMMAND test_rendered_view)
add_test(NAME test_rendered_view COMMAND test_rendered_view)
add_executable(test_new_features tests/test_new_features.cpp
src/generator.cpp src/compose.cpp src/format.cpp src/controller.cpp
src/editor.cpp src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_new_features PRIVATE src third_party/fadec)
target_link_libraries(test_new_features PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_new_features PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_new_features COMMAND test_new_features)
add_executable(test_type_selector tests/test_type_selector.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
add_executable(test_type_selector tests/test_type_selector.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_type_selector PRIVATE src third_party/fadec)
target_link_libraries(test_type_selector PRIVATE
target_include_directories(test_type_selector PRIVATE src third_party/fadec)
target_link_libraries(test_type_selector PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_type_selector PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_type_selector COMMAND test_type_selector)
if(WIN32)
target_link_libraries(test_type_selector PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_type_selector COMMAND test_type_selector)
add_executable(test_type_visibility tests/test_type_visibility.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(test_type_visibility PRIVATE src third_party/fadec)
target_link_libraries(test_type_visibility PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_type_visibility PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_type_visibility COMMAND test_type_visibility)
add_executable(test_options_dialog tests/test_options_dialog.cpp
add_executable(test_options_dialog tests/test_options_dialog.cpp
src/optionsdialog.cpp src/themes/theme.cpp src/themes/thememanager.cpp)
target_include_directories(test_options_dialog PRIVATE src)
target_link_libraries(test_options_dialog PRIVATE ${QT}::Widgets ${QT}::Test)
add_test(NAME test_options_dialog COMMAND test_options_dialog)
target_include_directories(test_options_dialog PRIVATE src)
target_link_libraries(test_options_dialog PRIVATE ${QT}::Widgets ${QT}::Test)
add_test(NAME test_options_dialog COMMAND test_options_dialog)
if(WIN32)
add_executable(test_windbg_provider tests/test_windbg_provider.cpp
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp)
target_include_directories(test_windbg_provider PRIVATE src plugins/WinDbgMemory)
target_link_libraries(test_windbg_provider PRIVATE
add_executable(test_source_provider tests/test_source_provider.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
src/typeselectorpopup.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS}
src/resources.qrc)
target_include_directories(test_source_provider PRIVATE src third_party/fadec)
target_link_libraries(test_source_provider PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test ${QT}::Svg
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(test_source_provider PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME test_source_provider COMMAND test_source_provider)
add_executable(test_scanner_ui tests/test_scanner_ui.cpp
src/scanner.cpp src/scannerpanel.cpp src/addressparser.cpp
src/themes/theme.cpp src/themes/thememanager.cpp)
target_include_directories(test_scanner_ui PRIVATE src)
target_link_libraries(test_scanner_ui PRIVATE
${QT}::Widgets ${QT}::Concurrent ${QT}::Test)
add_test(NAME test_scanner_ui COMMAND test_scanner_ui)
add_executable(test_mcp tests/test_mcp.cpp)
target_include_directories(test_mcp PRIVATE src)
target_link_libraries(test_mcp PRIVATE ${QT}::Core ${QT}::Network ${QT}::Test)
add_test(NAME test_mcp COMMAND test_mcp)
if(WIN32)
add_executable(test_windbg_provider tests/test_windbg_provider.cpp
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp
src/scanner.cpp)
target_include_directories(test_windbg_provider PRIVATE src plugins/WinDbgMemory)
target_link_libraries(test_windbg_provider PRIVATE
${QT}::Widgets ${QT}::Concurrent ${QT}::Test dbgeng ole32)
add_test(NAME test_windbg_provider COMMAND test_windbg_provider)
endif()
add_test(NAME test_windbg_provider COMMAND test_windbg_provider)
# Standalone test: proves whether CoInitializeSecurity is needed for DebugConnect
# Requires a running WinDbg debug server on port 5055
if(WIN32)
add_executable(test_com_security tests/test_com_security.cpp)
target_link_libraries(test_com_security PRIVATE dbgeng ole32 version)
add_test(NAME test_com_security COMMAND test_com_security)
endif()
add_executable(test_kernel_provider tests/test_kernel_provider.cpp
plugins/KernelMemory/KernelMemoryPlugin.cpp
src/processpicker.cpp src/processpicker.ui
src/scanner.cpp)
target_include_directories(test_kernel_provider PRIVATE
src plugins/KernelMemory)
target_link_libraries(test_kernel_provider PRIVATE
${QT}::Widgets ${QT}::Concurrent ${QT}::Test
psapi shell32 advapi32 ${_QT_WINEXTRAS})
add_test(NAME test_kernel_provider COMMAND test_kernel_provider)
endif()
# Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe
# that links the broadest set of Qt modules; all test exes share the same output dir)
if(TARGET ${QT}::windeployqt)
add_custom_target(deploy_tests ALL
add_executable(bench_large_class tests/bench_large_class.cpp
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
src/providerregistry.cpp
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
target_include_directories(bench_large_class PRIVATE src third_party/fadec)
target_link_libraries(bench_large_class PRIVATE
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
QScintilla::QScintilla)
if(WIN32)
target_link_libraries(bench_large_class PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME bench_large_class COMMAND bench_large_class)
add_executable(bench_project tests/bench_project.cpp)
target_include_directories(bench_project PRIVATE src)
target_link_libraries(bench_project PRIVATE ${QT}::Widgets ${QT}::Test)
if(WIN32)
target_link_libraries(bench_project PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
endif()
add_test(NAME bench_project COMMAND bench_project)
# Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe
# that links the broadest set of Qt modules; all test exes share the same output dir)
if(TARGET ${QT}::windeployqt)
add_custom_target(deploy_tests ALL
COMMAND $<TARGET_FILE:${QT}::windeployqt>
--no-compiler-runtime --no-translations
--no-opengl-sw --no-system-d3d-compiler
@@ -337,12 +595,16 @@ if(BUILD_TESTING)
DEPENDS test_controller
COMMENT "Deploying Qt runtime DLLs for tests..."
)
endif()
endif()
endif() # BUILD_UI_TESTS
endif()
add_subdirectory(plugins/ProcessMemory)
if(NOT APPLE)
add_subdirectory(plugins/ProcessMemory)
add_subdirectory(plugins/RemoteProcessMemory)
endif()
if(WIN32)
add_subdirectory(plugins/KernelMemory)
add_subdirectory(plugins/WinDbgMemory)
add_subdirectory(plugins/RcNetPluginCompatLayer)
endif()

210
README.md
View File

@@ -1,43 +1,195 @@
This tool helps you inspect raw bytes and interpret them as types (structs, arrays, primitives, pointers, padding) instead of just hex. It is essentially a debugging tool for figuring out unknown data structures either runtime or from some static source.
<div align="center">
## State
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/RECLASS_LIGHTMODE.svg" height="170">
<img src="docs/RECLASS_DARKMODE.svg" alt="Reclass" height="170" />
</picture>
- MCP (Model Context Protocol) bridge via `ReclassMcpBridge.exe`. The server starts by default and can be stopped from the File menu. It exposes all tool functionality to any MCP-compatible client (e.g. Claude Code) and falls back to UI prompts when the client requests something not yet covered by tools. To connect, add this to your MCP client config (e.g. `.mcp.json`):
```json
{
"mcpServers": {
"ReclassMcpBridge": {
"command": "path/to/build/ReclassMcpBridge.exe",
"args": []
}
**A structured binary editor for reverse engineering — inspect raw bytes as typed structs, arrays, and pointers.<p>Built from scratch as a modern replacement for ReClass.NET and ReClassEx**
[Download](https://github.com/IChooseYou/Reclass/releases) · [Build Instructions](#build) · [MCP Integration](#mcp-integration) · [Alternatives](#alternatives)
[![Build](https://github.com/IChooseYou/Reclass/actions/workflows/build.yml/badge.svg)](https://github.com/IChooseYou/Reclass/actions/workflows/build.yml)
[![License](https://img.shields.io/github/license/IChooseYou/Reclass)](LICENSE)
[![Release](https://img.shields.io/github/v/release/IChooseYou/Reclass?label=snapshot)](https://github.com/IChooseYou/Reclass/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-blue)]()
</div>
Reclass helps you inspect raw bytes and interpret them as types (structs, arrays, primitives, pointers, padding) instead of just hex. It is a debugging tool for figuring out unknown data structures — either at runtime from a live process, or from a static source like a binary file or crash dump.
Built with C++17, Qt 6 (Qt 5 also supported), and QScintilla. The entire editor surface is rendered as formatted plain text with inline editing, fold markers, and hex/ASCII previews.
## Screenshots
![Base address tooltip with expression cheat sheet](docs/README_PIC5.png)
![Data source picker with saved sources](docs/README_PIC4.png)
![Windows — VTable with value history popup](docs/README_PIC1.png)
![macOS — project tree with kernel struct inspection](docs/README_PIC2.png)
![Memory scanner](docs/README_PIC3.png)
## Features
### Editor
- **Structured binary view** — render raw bytes as typed fields with columnar alignment
- **Inline editing** — click to edit type names, field names, values, base addresses, array metadata, pointer targets, enum members, bitfield members, static expressions, and comments — all with real-time validation
- **Tab-cycling** — tab through editable fields within a line
- **Type autocomplete** — cached popup type picker with search/filter for struct targets
- **Multi-select** — Ctrl+click individual nodes or Shift+click for range selection
- **Split views** — multiple synchronized editor panes over the same document
- **Find bar** — Ctrl+F in-editor search with indicator highlighting
- **Fold/collapse** — expand and collapse structs, arrays, and pointer expansions with embedded fold indicators
- **Hex + ASCII columns** — raw byte previews alongside the structured view with per-byte change highlighting
### Live Memory Analysis
- **Auto-refresh** — configurable interval (default 660ms) with async page-based reads for non-blocking UI
- **Value history & heatmap** — per-node ring buffer (10 samples with timestamps), color-coded heat indicators (static/cold/warm/hot) based on change frequency
- **Changed-byte highlighting** — per-byte change indicators within hex preview lines
- **Memory write-back** — edit values inline, writes propagate through the provider to live process memory
- **Pointer chasing** — automatic reads of dereferenced memory regions across pointer chains
- **Address parser** — formula expressions like `<module.exe>+0x1A0`, pointer dereference chains, symbol resolution
### Undo / Redo
Full command stack with 15 undoable operations: ChangeKind, Rename, Collapse, Insert, Remove, ChangeBase, WriteBytes, ChangeArrayMeta, ChangePointerRef, ChangeStructTypeName, ChangeClassKeyword, ChangeOffset, ChangeEnumMembers, ChangeOffsetExpr, ToggleStatic. Batch macro support for multi-node operations.
### Import / Export
| Format | Import | Export |
|--------|:------:|:------:|
| **Native JSON (.rcx)** | Full tree + metadata | Full tree + metadata |
| **C/C++ source** | Struct/class/union/enum parsing with offset comments | Header generation with optional static asserts |
| **ReClass XML** | Full compatibility with ReClass Classic | Full compatibility |
| **PDB symbols (Windows)** | UDT enumeration with selective recursive import via raw_pdb — no DIA SDK dependency | |
### Workspace & Navigation
- **Multi-document tabs** — MDI interface, one document per tab
- **Workspace dock** — project explorer tree with struct/enum/union icons, sorted by field count, quick navigation to members
- **Scanner dock** — integrated memory search panel
- **Dual view mode** — switch between ReClass tree view and rendered C/C++ output per tab
- **View root** — focus on a specific struct, hiding all others
- **Scroll to node** — programmatic navigation to any node by ID
## Data Sources
- **File** — open any binary file and inspect its contents as structured data
- **Process** — attach to a live process and read its memory in real time (Windows/Linux)
- **Kernel driver** — Windows kernel driver (IOCTL) for process memory, physical memory, page table walking, and CR3/VTOP translation
- **Remote Process** — read another process's memory over TCP with cross-architecture 32/64-bit support
- **WinDbg** — connect to live WinDbg debugging sessions or load crash dumps
- **Saved sources** — quick-switch between recently used data sources per tab
## Plugin System
DLL plugins loaded from a `Plugins` folder, auto or manual.
**Bundled plugins:**
| Plugin | Description |
|--------|-------------|
| **Process memory** | Attach to local processes on Windows and Linux — PID-based, with symbol resolution and module/region enumeration |
| **Kernel memory** | Windows kernel driver (IOCTL) for reading/writing process and physical memory, CR3 queries, virtual-to-physical translation, and full 4-level page table walking — supports 4KB, 2MB, and 1GB pages |
| **WinDbg** | Access data from live WinDbg debugging sessions |
| **Remote process memory** | TCP RPC-based remote process access with cross-architecture support |
| **ReClass.NET compatibility** | Load existing ReClass.NET native DLL plugins directly; optional .NET CLR hosting for managed plugins |
## MCP Integration
Built-in [Model Context Protocol](https://modelcontextprotocol.io/) bridge via `ReclassMcpBridge` — the first reverse engineering tool with native AI/LLM integration. The server uses JSON-RPC 2.0 over named pipes and can be toggled from the Tools menu or auto-started on launch.
**Available tools:**
| Tool | Description |
|------|-------------|
| `projectState` | Read current tree structure, base address, tab state |
| `treeApply` | Apply structural command deltas to the node tree |
| `sourceSwitch` | Switch the active data source |
| `hexRead` | Read bytes at an address |
| `hexWrite` | Write bytes at an address |
| `statusSet` | Update the status bar text |
| `uiAction` | Trigger menu actions programmatically |
| `treeSearch` | Search nodes by name or type |
| `nodeHistory` | Query value change history for a node |
**Notifications:** `notifyTreeChanged`, `notifyDataChanged`
A standalone stdio-to-pipe bridge binary is built alongside the main application. To connect, add this to your MCP client config (e.g. `.mcp.json`):
```json
{
"mcpServers": {
"ReclassMcpBridge": {
"command": "path/to/build/ReclassMcpBridge",
"args": []
}
}
```
}
```
## Build
1. Prerequisites
### Prerequisites
- Qt 6 with MinGW - Qt Online Installer https://doc.qt.io/qt-6/qt-online-installation.html , note to select MinGW kit + CMake/Ninja from Tools section (online installers index: https://download.qt.io/official_releases/online_installers/)
- CMake 3.20+ - https://cmake.org/download/ - bundled with Qt
- windeployqt docs - https://doc.qt.io/qt-6/windows-deployment.html
- **Qt 6** (or Qt 5) with MinGW — [Qt Online Installer](https://doc.qt.io/qt-6/qt-online-installation.html) (select MinGW kit + CMake/Ninja from the Tools section)
- **CMake 3.20+** — [cmake.org](https://cmake.org/download/) (bundled with Qt)
- **Ninja** — bundled with the Qt installer
2. Quick Build (relies on powershell| for manual build skip to step 3)
### Quick Build
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
cd Reclass
.\scripts\build_qscintilla.ps1
.\scripts\build.ps1
^ script above tries to autodetect Qt install (as we learned not everyone installs to C:/Qt/)
```bash
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
cd Reclass
.\scripts\build_qscintilla.ps1
.\scripts\build.ps1
```
3. Manual Build
The build script auto-detects your Qt install location.
Step by step for peoplewho want to run commands themselves:
1. Clone with --recurse-submodules (+ fallback git submodule update --init --recursive)
2. Build QScintilla: qmake + mingw32-make in third_party/qscintilla/src
3. CMake configure + build with -DCMAKE_PREFIX_PATH
4. optionallly windeployqt the exe
### macOS Build
```bash
./scripts/build_macos.sh --qt-dir /opt/homebrew/opt/qt --build-type Release --package
```
If you installed Qt via Homebrew, `--qt-dir /opt/homebrew/opt/qt` is typical on Apple Silicon. You can also set `QTDIR` or `Qt6_DIR` instead of passing `--qt-dir`.
Note: macOS Gatekeeper may block unsigned apps. If the app won't open, go to **System Settings > Privacy & Security** and click **Open Anyway**.
### Manual Build (MinGW)
1. Clone with `--recurse-submodules` (or run `git submodule update --init --recursive` after cloning)
2. Build QScintilla: `qmake` + `mingw32-make` in `third_party/qscintilla/src`
3. Configure and build:
```bash
cmake -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.x/mingw_64
cmake --build build
```
4. Optionally run `windeployqt` on the output executable
### Visual Studio 2022+
The `msvc/` folder contains a ready-made solution (`Reclass.slnx`) with projects for the main application, all plugins, and third-party libraries. Requires the [Qt Visual Studio Tools](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022) extension with a Qt 6 MSVC kit configured.
### Running Tests
```bash
ctest --test-dir build --output-on-failure
```
30 tests covering composition, serialization, undo/redo, import/export, provider switching, type visibility, validation, scanning, and rendering.
## Alternatives
- ReClass.NET (reclass.net) - https://github.com/ReClassNET/ReClass.NET
- ReClassEx - https://github.com/ajkhoury/ReClassEx
- [ReClass.NET](https://github.com/ReClassNET/ReClass.NET)
- [ReClassEx](https://github.com/ajkhoury/ReClassEx)
<div align="center">
<sub>MIT License</sub>
</div>

View File

@@ -1,7 +1,7 @@
# cmake/deploy.cmake - Dual-mode script for deploying Qt runtime DLLs
#
# Script mode: cmake -P deploy.cmake <target_exe> <windeployqt>
# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target)
# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target + post-build)
if(CMAKE_SCRIPT_MODE_FILE)
set(TARGET_EXE ${CMAKE_ARGV3})
@@ -17,7 +17,6 @@ if(CMAKE_SCRIPT_MODE_FILE)
execute_process(
COMMAND ${WINDEPLOYQT}
--pdb
--no-compiler-runtime
--no-translations
--no-opengl-sw
@@ -67,6 +66,7 @@ if(NOT TARGET ${QT}::windeployqt AND TARGET ${QT}::qmake)
endif()
if(TARGET ${QT}::windeployqt)
# Standalone "deploy" target (can still be invoked manually)
add_custom_target(deploy
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
$<TARGET_FILE:Reclass>
@@ -79,4 +79,13 @@ if(TARGET ${QT}::windeployqt)
set_target_properties(deploy PROPERTIES
ADDITIONAL_CLEAN_FILES $<TARGET_FILE_DIR:Reclass>/.qt_deployed
)
# Auto-deploy as post-build step so the correct Qt DLLs are always next
# to the exe. Without this, MSVC builds load whatever Qt DLLs happen to
# be in PATH (often MinGW ones), causing instant ABI-mismatch crashes.
add_custom_command(TARGET Reclass POST_BUILD
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
$<TARGET_FILE:Reclass>
$<TARGET_FILE:${QT}::windeployqt>
COMMENT "Auto-deploying Qt runtime DLLs...")
endif()

29
cmake/raw_pdb_prefix.h Normal file
View File

@@ -0,0 +1,29 @@
// Force-included before every raw_pdb translation unit (and consumers).
// PDB_CRT.h forward-declares printf/memcmp/etc with extern "C" __cdecl,
// which conflicts with MinGW's CRT headers (C++ linkage, no __cdecl).
//
// Fix: include the real CRT headers, then include PDB_CRT.h with function
// names macro-renamed to harmless dummies. This triggers #pragma once so
// no raw_pdb source file ever processes PDB_CRT.h's conflicting declarations.
//
// Guarded with __cplusplus because PUBLIC propagation applies this to C
// sources (fadec) where PDB_CRT.h is irrelevant and <cstdio> doesn't exist.
#ifdef __cplusplus
#include <cstdio>
#include <cstring>
#undef __cdecl
#define __cdecl
#define printf _pdb_crt_unused_printf
#define memcmp _pdb_crt_unused_memcmp
#define memcpy _pdb_crt_unused_memcpy
#define strlen _pdb_crt_unused_strlen
#define strcmp _pdb_crt_unused_strcmp
#include "Foundation/PDB_CRT.h"
#undef printf
#undef memcmp
#undef memcpy
#undef strlen
#undef strcmp
#endif

BIN
docs/README_PIC1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/README_PIC2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

BIN
docs/README_PIC3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
docs/README_PIC4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/README_PIC5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

160
docs/RECLASS_DARKMODE.svg Normal file
View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 186.01 52.79">
<defs>
<style>
.cls-1 {
fill: url(#Unbenannter_Verlauf_130-2);
}
.cls-2 {
fill: url(#Unbenannter_Verlauf_236-2);
}
.cls-3 {
fill: url(#Unbenannter_Verlauf_225-2);
}
.cls-4 {
fill: #1f2939;
}
.cls-5 {
fill: #5d9bd4;
}
.cls-6 {
fill: #1e3e88;
}
.cls-7 {
fill: #6e809a;
}
.cls-8 {
fill: url(#Unbenannter_Verlauf_225);
}
.cls-9 {
fill: url(#Unbenannter_Verlauf_236);
}
.cls-10 {
fill: url(#Unbenannter_Verlauf_130);
}
.cls-11 {
fill: url(#Unbenannter_Verlauf_170);
}
.cls-12 {
fill: url(#Unbenannter_Verlauf_161);
}
.cls-13 {
fill: url(#Unbenannter_Verlauf_183);
}
.cls-14 {
fill: #b06ba9;
}
.cls-15 {
fill: #826415;
}
.cls-16 {
fill: #e2aa11;
}
.cls-17 {
fill: #893089;
}
</style>
<linearGradient id="Unbenannter_Verlauf_161" data-name="Unbenannter Verlauf 161" x1="8.33" y1="8.33" x2="18.11" y2="18.11" gradientTransform="translate(13.22 -5.47) rotate(45)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#f3db78"/>
<stop offset=".19" stop-color="#f4e188"/>
<stop offset=".34" stop-color="#f4e38d"/>
<stop offset=".38" stop-color="#f4df81"/>
<stop offset=".47" stop-color="#f5d86f"/>
<stop offset=".57" stop-color="#f5d463"/>
<stop offset=".67" stop-color="#f6d360"/>
<stop offset=".89" stop-color="#f1cc53"/>
<stop offset="1" stop-color="#efbe33"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_130" data-name="Unbenannter Verlauf 130" x1=".41" y1="15.46" x2="10.98" y2="26.03" gradientTransform="translate(-4.95 39.45) rotate(-135)" gradientUnits="userSpaceOnUse">
<stop offset=".18" stop-color="#e2aa11"/>
<stop offset=".91" stop-color="#826415"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_130-2" data-name="Unbenannter Verlauf 130" x1="15.46" y1=".41" x2="26.03" y2="10.98" gradientTransform="translate(31.39 24.39) rotate(-135)" xlink:href="#Unbenannter_Verlauf_130"/>
<linearGradient id="Unbenannter_Verlauf_170" data-name="Unbenannter Verlauf 170" x1="34.97" y1="15.65" x2="42.34" y2="23.02" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#deb0d3"/>
<stop offset=".15" stop-color="#e1b5d6"/>
<stop offset=".3" stop-color="#e3b8d7"/>
<stop offset=".4" stop-color="#d7a8cd"/>
<stop offset=".53" stop-color="#cf9cc7"/>
<stop offset=".67" stop-color="#cd99c5"/>
<stop offset=".89" stop-color="#c68abc"/>
<stop offset="1" stop-color="#bb7db4"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_225" data-name="Unbenannter Verlauf 225" x1="28.78" y1="20.14" x2="36.87" y2="28.24" gradientTransform="translate(.63 .63) rotate(-.12) skewX(-.25)" gradientUnits="userSpaceOnUse">
<stop offset=".19" stop-color="#b06ba9"/>
<stop offset=".87" stop-color="#893089"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_225-2" data-name="Unbenannter Verlauf 225" x1="39.45" y1="9.43" x2="47.55" y2="17.53" xlink:href="#Unbenannter_Verlauf_225"/>
<linearGradient id="Unbenannter_Verlauf_183" data-name="Unbenannter Verlauf 183" x1="34.88" y1="39.45" x2="42.29" y2="46.86" gradientTransform="translate(41.82 -14.64) rotate(45)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#91c4eb"/>
<stop offset=".2" stop-color="#9dc9ed"/>
<stop offset=".33" stop-color="#96c6ec"/>
<stop offset=".35" stop-color="#91c3ea"/>
<stop offset=".45" stop-color="#7fb8e5"/>
<stop offset=".56" stop-color="#73b2e2"/>
<stop offset=".67" stop-color="#70b0e1"/>
<stop offset=".89" stop-color="#60a7dc"/>
<stop offset="1" stop-color="#4d9bd5"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_236" data-name="Unbenannter Verlauf 236" x1="28.83" y1="43.9" x2="36.92" y2="51.99" gradientTransform="translate(22.68 105.31) rotate(-135.12) skewX(-.25)" gradientUnits="userSpaceOnUse">
<stop offset=".19" stop-color="#5d9bd4"/>
<stop offset=".87" stop-color="#1e3e88"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_236-2" data-name="Unbenannter Verlauf 236" x1="39.51" y1="33.23" x2="47.59" y2="41.32" gradientTransform="translate(48.45 94.63) rotate(-135.12) skewX(-.25)" xlink:href="#Unbenannter_Verlauf_236"/>
</defs>
<g>
<rect class="cls-7" x="22.48" y="16.85" width="1.76" height="25.1"/>
<rect class="cls-7" x="17.08" y="16.85" width="19.7" height="1.82"/>
<rect class="cls-7" x="22.48" y="40.19" width="11.9" height="1.76"/>
<g>
<rect class="cls-12" x="2.56" y="6.31" width="21.31" height="13.82" transform="translate(-5.48 13.22) rotate(-45)"/>
<rect class="cls-15" x="17.52" y="6.88" width="1.15" height="22.44" transform="translate(18.1 -7.49) rotate(45)"/>
<g>
<rect class="cls-16" x="7.76" y="-2.88" width="1.15" height="22.44" transform="translate(8.34 -3.45) rotate(45)"/>
<rect class="cls-10" x="5.12" y="13.27" width="1.15" height="14.95" transform="translate(24.39 31.39) rotate(135)"/>
<rect class="cls-1" x="20.17" y="-1.78" width="1.15" height="14.95" transform="translate(39.45 -4.95) rotate(135)"/>
</g>
</g>
<g>
<polygon class="cls-11" points="40.33 10.29 29.64 21.02 36.98 28.38 47.67 17.66 40.33 10.29"/>
<polygon class="cls-17" points="37.01 29.1 36.29 28.38 47.68 16.96 48.39 17.68 37.01 29.1"/>
<polygon class="cls-14" points="29.67 21.74 28.95 21.02 40.34 9.6 41.05 10.31 29.67 21.74"/>
<polygon class="cls-8" points="28.95 21.02 29.67 20.3 37.72 28.38 37 29.1 28.95 21.02"/>
<polygon class="cls-3" points="39.63 10.31 40.34 9.6 48.39 17.67 47.68 18.39 39.63 10.31"/>
</g>
<g>
<rect class="cls-13" x="30.96" y="37.92" width="15.26" height="10.48" transform="translate(-19.21 39.92) rotate(-45)"/>
<g>
<rect class="cls-9" x="32.83" y="42.71" width="1.01" height="11.38" transform="translate(91.13 59.06) rotate(135)"/>
<rect class="cls-2" x="43.5" y="32.04" width="1.01" height="11.38" transform="translate(101.81 33.29) rotate(135)"/>
<rect class="cls-6" x="41.84" y="38.69" width="1.01" height="16.1" transform="translate(45.45 -16.25) rotate(45)"/>
<rect class="cls-5" x="34.5" y="31.35" width="1.01" height="16.1" transform="translate(38.11 -13.21) rotate(45)"/>
</g>
</g>
</g>
<g>
<path class="cls-4" d="M53.66,30.51v11.46h-2.57v-25.13h9.36c5.04,0,7.72,2.72,7.72,6.65,0,3.22-1.89,5.26-4.51,5.89,2.34.58,4.09,2.2,4.09,6.5v1.02c0,1.74-.11,4.07.33,5.07h-2.54c-.46-1.08-.39-3.1-.39-5.34v-.6c0-3.87-1.12-5.52-5.79-5.52h-5.7ZM53.66,28.26h5.79c4.15,0,6.03-1.56,6.03-4.64,0-2.9-1.89-4.54-5.57-4.54h-6.25v9.18Z"/>
<path class="cls-4" d="M86.55,29.87h-12.65v9.79h13.88l-.35,2.3h-16.06v-25.12h15.81v2.27h-13.28v8.49h12.65v2.27Z"/>
<path class="cls-4" d="M109.12,35.04c-1.15,4.11-4.2,7.19-9.68,7.19-7.34,0-11.13-5.72-11.13-12.79s3.76-12.96,11.21-12.96c5.64,0,8.84,3.18,9.63,7.37h-2.56c-1.04-3.02-3.01-5.13-7.18-5.13-5.92,0-8.38,5.4-8.38,10.66s2.39,10.62,8.52,10.62c3.99,0,5.89-2.16,7.01-4.95h2.57Z"/>
<path class="cls-4" d="M111.62,16.84h2.56v22.82h13.3l-.41,2.27h-15.46v-25.09Z"/>
<path class="cls-4" d="M133.03,33.77l-2.97,8.16h-2.58l9.09-25.09h3.11l9.48,25.09h-2.76l-3.05-8.16h-10.32ZM142.61,31.5c-2.61-7.07-3.99-10.62-4.51-12.4h-.04c-.61,2-2.16,6.36-4.27,12.4h8.82Z"/>
<path class="cls-4" d="M151.68,35.08c.72,3.19,2.87,5,6.77,5,4.28,0,5.95-2.09,5.95-4.65,0-2.69-1.25-4.29-6.55-5.59-5.58-1.38-7.76-3.23-7.76-6.81s2.56-6.55,8.04-6.55,8.11,3.41,8.44,6.57h-2.63c-.52-2.48-2.11-4.37-5.93-4.37-3.37,0-5.22,1.55-5.22,4.16s1.54,3.59,6.07,4.7c7.1,1.75,8.24,4.56,8.24,7.67,0,3.85-2.83,7.03-8.78,7.03-6.29,0-8.78-3.56-9.27-7.15h2.63Z"/>
<path class="cls-4" d="M170.59,35.08c.72,3.19,2.87,5,6.77,5,4.28,0,5.95-2.09,5.95-4.65,0-2.69-1.25-4.29-6.55-5.59-5.58-1.38-7.76-3.23-7.76-6.81s2.56-6.55,8.04-6.55,8.11,3.41,8.44,6.57h-2.63c-.52-2.48-2.11-4.37-5.93-4.37-3.37,0-5.22,1.55-5.22,4.16s1.54,3.59,6.07,4.7c7.1,1.75,8.25,4.56,8.25,7.67,0,3.85-2.83,7.03-8.78,7.03-6.29,0-8.78-3.56-9.27-7.15h2.63Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

160
docs/RECLASS_LIGHTMODE.svg Normal file
View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 185.55 52.66">
<defs>
<style>
.cls-1 {
fill: url(#Unbenannter_Verlauf_130-2);
}
.cls-2 {
fill: url(#Unbenannter_Verlauf_236-2);
}
.cls-3 {
fill: url(#Unbenannter_Verlauf_225-2);
}
.cls-4 {
fill: #5d9bd4;
}
.cls-5 {
fill: #e3e8f0;
}
.cls-6 {
fill: #1e3e88;
}
.cls-7 {
fill: #6e809a;
}
.cls-8 {
fill: url(#Unbenannter_Verlauf_225);
}
.cls-9 {
fill: url(#Unbenannter_Verlauf_236);
}
.cls-10 {
fill: url(#Unbenannter_Verlauf_130);
}
.cls-11 {
fill: url(#Unbenannter_Verlauf_170);
}
.cls-12 {
fill: url(#Unbenannter_Verlauf_161);
}
.cls-13 {
fill: url(#Unbenannter_Verlauf_183);
}
.cls-14 {
fill: #b06ba9;
}
.cls-15 {
fill: #826415;
}
.cls-16 {
fill: #e2aa11;
}
.cls-17 {
fill: #893089;
}
</style>
<linearGradient id="Unbenannter_Verlauf_161" data-name="Unbenannter Verlauf 161" x1="8.31" y1="8.31" x2="18.06" y2="18.06" gradientTransform="translate(13.19 -5.46) rotate(45)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#f3db78"/>
<stop offset=".19" stop-color="#f4e188"/>
<stop offset=".34" stop-color="#f4e38d"/>
<stop offset=".38" stop-color="#f4df81"/>
<stop offset=".47" stop-color="#f5d86f"/>
<stop offset=".57" stop-color="#f5d463"/>
<stop offset=".67" stop-color="#f6d360"/>
<stop offset=".89" stop-color="#f1cc53"/>
<stop offset="1" stop-color="#efbe33"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_130" data-name="Unbenannter Verlauf 130" x1=".41" y1="15.42" x2="10.95" y2="25.97" gradientTransform="translate(-4.94 39.35) rotate(-135)" gradientUnits="userSpaceOnUse">
<stop offset=".18" stop-color="#e2aa11"/>
<stop offset=".91" stop-color="#826415"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_130-2" data-name="Unbenannter Verlauf 130" x1="15.42" y1=".41" x2="25.97" y2="10.95" gradientTransform="translate(31.32 24.33) rotate(-135)" xlink:href="#Unbenannter_Verlauf_130"/>
<linearGradient id="Unbenannter_Verlauf_170" data-name="Unbenannter Verlauf 170" x1="34.88" y1="15.61" x2="42.24" y2="22.97" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#deb0d3"/>
<stop offset=".15" stop-color="#e1b5d6"/>
<stop offset=".3" stop-color="#e3b8d7"/>
<stop offset=".4" stop-color="#d7a8cd"/>
<stop offset=".53" stop-color="#cf9cc7"/>
<stop offset=".67" stop-color="#cd99c5"/>
<stop offset=".89" stop-color="#c68abc"/>
<stop offset="1" stop-color="#bb7db4"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_225" data-name="Unbenannter Verlauf 225" x1="28.7" y1="20.09" x2="36.78" y2="28.17" gradientTransform="translate(.63 .63) rotate(-.12) skewX(-.25)" gradientUnits="userSpaceOnUse">
<stop offset=".19" stop-color="#b06ba9"/>
<stop offset=".87" stop-color="#893089"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_225-2" data-name="Unbenannter Verlauf 225" x1="39.35" y1="9.41" x2="47.43" y2="17.49" xlink:href="#Unbenannter_Verlauf_225"/>
<linearGradient id="Unbenannter_Verlauf_183" data-name="Unbenannter Verlauf 183" x1="34.79" y1="39.35" x2="42.18" y2="46.74" gradientTransform="translate(41.71 -14.61) rotate(45)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#91c4eb"/>
<stop offset=".2" stop-color="#9dc9ed"/>
<stop offset=".33" stop-color="#96c6ec"/>
<stop offset=".35" stop-color="#91c3ea"/>
<stop offset=".45" stop-color="#7fb8e5"/>
<stop offset=".56" stop-color="#73b2e2"/>
<stop offset=".67" stop-color="#70b0e1"/>
<stop offset=".89" stop-color="#60a7dc"/>
<stop offset="1" stop-color="#4d9bd5"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_236" data-name="Unbenannter Verlauf 236" x1="28.76" y1="43.79" x2="36.83" y2="51.86" gradientTransform="translate(22.62 105.05) rotate(-135.12) skewX(-.25)" gradientUnits="userSpaceOnUse">
<stop offset=".19" stop-color="#5d9bd4"/>
<stop offset=".87" stop-color="#1e3e88"/>
</linearGradient>
<linearGradient id="Unbenannter_Verlauf_236-2" data-name="Unbenannter Verlauf 236" x1="39.41" y1="33.15" x2="47.47" y2="41.21" gradientTransform="translate(48.33 94.4) rotate(-135.12) skewX(-.25)" xlink:href="#Unbenannter_Verlauf_236"/>
</defs>
<g>
<rect class="cls-7" x="22.43" y="16.81" width="1.75" height="25.04"/>
<rect class="cls-7" x="17.04" y="16.81" width="19.66" height="1.82"/>
<rect class="cls-7" x="22.43" y="40.09" width="11.87" height="1.75"/>
<g>
<rect class="cls-12" x="2.56" y="6.29" width="21.26" height="13.79" transform="translate(-5.46 13.19) rotate(-45)"/>
<rect class="cls-15" x="17.48" y="6.87" width="1.15" height="22.38" transform="translate(18.06 -7.47) rotate(45)"/>
<g>
<rect class="cls-16" x="7.74" y="-2.87" width="1.15" height="22.38" transform="translate(8.32 -3.45) rotate(45)"/>
<rect class="cls-10" x="5.1" y="13.24" width="1.15" height="14.92" transform="translate(24.33 31.32) rotate(135)"/>
<rect class="cls-1" x="20.12" y="-1.78" width="1.15" height="14.92" transform="translate(39.35 -4.94) rotate(135)"/>
</g>
</g>
<g>
<polygon class="cls-11" points="40.23 10.26 29.56 20.96 36.89 28.31 47.56 17.61 40.23 10.26"/>
<polygon class="cls-17" points="36.91 29.03 36.2 28.31 47.56 16.92 48.27 17.63 36.91 29.03"/>
<polygon class="cls-14" points="29.59 21.68 28.88 20.97 40.24 9.57 40.95 10.29 29.59 21.68"/>
<polygon class="cls-8" points="28.88 20.97 29.59 20.25 37.62 28.31 36.91 29.02 28.88 20.97"/>
<polygon class="cls-3" points="39.53 10.29 40.24 9.57 48.27 17.63 47.56 18.34 39.53 10.29"/>
</g>
<g>
<rect class="cls-13" x="30.88" y="37.82" width="15.22" height="10.45" transform="translate(-19.17 39.82) rotate(-45)"/>
<g>
<rect class="cls-9" x="32.75" y="42.61" width="1.01" height="11.36" transform="translate(90.91 58.91) rotate(135)"/>
<rect class="cls-2" x="43.4" y="31.96" width="1.01" height="11.36" transform="translate(101.56 33.21) rotate(135)"/>
<rect class="cls-6" x="41.73" y="38.59" width="1.01" height="16.06" transform="translate(45.34 -16.21) rotate(45)"/>
<rect class="cls-4" x="34.41" y="31.27" width="1.01" height="16.06" transform="translate(38.02 -13.18) rotate(45)"/>
</g>
</g>
</g>
<g>
<path class="cls-5" d="M53.53,30.44v11.43h-2.56v-25.07h9.34c5.03,0,7.7,2.71,7.7,6.63,0,3.21-1.88,5.24-4.5,5.87,2.33.58,4.08,2.19,4.08,6.49v1.01c0,1.74-.11,4.06.33,5.06h-2.54c-.46-1.08-.39-3.09-.39-5.33v-.59c0-3.87-1.12-5.51-5.78-5.51h-5.68ZM53.53,28.19h5.77c4.14,0,6.02-1.55,6.02-4.63,0-2.89-1.88-4.53-5.55-4.53h-6.24v9.16Z"/>
<path class="cls-5" d="M86.34,29.8h-12.62v9.77h13.84l-.35,2.3h-16.02v-25.06h15.77v2.26h-13.25v8.47h12.62v2.26Z"/>
<path class="cls-5" d="M108.85,34.96c-1.15,4.09-4.19,7.17-9.65,7.17-7.32,0-11.11-5.7-11.11-12.76s3.75-12.93,11.18-12.93c5.63,0,8.82,3.17,9.6,7.35h-2.56c-1.03-3.02-3-5.12-7.16-5.12-5.91,0-8.36,5.39-8.36,10.63s2.38,10.6,8.5,10.6c3.98,0,5.88-2.16,6.99-4.94h2.56Z"/>
<path class="cls-5" d="M111.34,16.8h2.56v22.77h13.27l-.4,2.26h-15.42v-25.03Z"/>
<path class="cls-5" d="M132.69,33.69l-2.96,8.14h-2.57l9.07-25.03h3.1l9.45,25.03h-2.75l-3.04-8.14h-10.3ZM142.25,31.43c-2.61-7.05-3.98-10.59-4.5-12.37h-.04c-.61,2-2.15,6.34-4.26,12.37h8.8Z"/>
<path class="cls-5" d="M151.31,34.99c.72,3.18,2.86,4.99,6.75,4.99,4.27,0,5.93-2.08,5.93-4.64,0-2.68-1.24-4.28-6.53-5.57-5.57-1.37-7.75-3.23-7.75-6.8s2.55-6.54,8.02-6.54,8.09,3.4,8.42,6.55h-2.62c-.52-2.48-2.11-4.36-5.91-4.36-3.36,0-5.21,1.54-5.21,4.15s1.54,3.58,6.05,4.69c7.09,1.75,8.22,4.55,8.22,7.65,0,3.84-2.82,7.02-8.76,7.02-6.27,0-8.75-3.55-9.24-7.13h2.62Z"/>
<path class="cls-5" d="M170.17,34.99c.72,3.18,2.86,4.99,6.75,4.99,4.27,0,5.93-2.08,5.93-4.64,0-2.68-1.24-4.28-6.53-5.57-5.57-1.37-7.75-3.23-7.75-6.8s2.55-6.54,8.02-6.54,8.09,3.4,8.42,6.55h-2.62c-.52-2.48-2.11-4.36-5.91-4.36-3.36,0-5.21,1.54-5.21,4.15s1.54,3.58,6.05,4.69c7.09,1.75,8.22,4.55,8.22,7.65,0,3.84-2.82,7.02-8.76,7.02-6.27,0-8.75-3.55-9.24-7.13h2.62Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{8792F51B-4951-4BAD-B130-2F0EFDEFF64B}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<RootNamespace>ProcessMemoryPlugin</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\Plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>psapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>psapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<QtMoc Include="..\src\processpicker.h" />
</ItemGroup>
<ItemGroup>
<QtUic Include="..\src\processpicker.ui" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\plugins\ProcessMemory\ProcessMemoryPlugin.h" />
<ClInclude Include="..\src\iplugin.h" />
<ClInclude Include="..\src\providers\provider.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\plugins\ProcessMemory\ProcessMemoryPlugin.cpp" />
<ClCompile Include="..\src\processpicker.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets" />
</Project>

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6B775E9C-9CB6-45FD-86A0-BE948A778969}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<RootNamespace>RcNetCompatPlugin</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\Plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<PreprocessorDefinitions>HAS_CLR_BRIDGE=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>HAS_CLR_BRIDGE=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<QtMoc Include="..\src\processpicker.h" />
</ItemGroup>
<ItemGroup>
<QtUic Include="..\src\processpicker.ui" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\plugins\RcNetPluginCompatLayer\RcNetCompatPlugin.h" />
<ClInclude Include="..\plugins\RcNetPluginCompatLayer\RcNetCompatProvider.h" />
<ClInclude Include="..\plugins\RcNetPluginCompatLayer\ReClassNET_Plugin.hpp" />
<ClInclude Include="..\plugins\RcNetPluginCompatLayer\ClrHost.h" />
<ClInclude Include="..\src\iplugin.h" />
<ClInclude Include="..\src\providers\provider.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\plugins\RcNetPluginCompatLayer\RcNetCompatPlugin.cpp" />
<ClCompile Include="..\plugins\RcNetPluginCompatLayer\RcNetCompatProvider.cpp" />
<ClCompile Include="..\plugins\RcNetPluginCompatLayer\ClrHost.cpp" />
<ClCompile Include="..\src\processpicker.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets" />
</Project>

17
msvc/Reclass.slnx Normal file
View File

@@ -0,0 +1,17 @@
<Solution>
<Configurations>
<Platform Name="x64" />
</Configurations>
<Folder Name="/plugins/">
<Project Path="ProcessMemoryPlugin.vcxproj" Id="8792f51b-4951-4bad-b130-2f0efdeff64b" />
<Project Path="WinDbgMemoryPlugin.vcxproj" Id="e25d358e-20f0-448b-bb2f-55e9d1f8e7ca" />
<Project Path="RemoteProcessMemoryPlugin.vcxproj" Id="39e2ddf6-cb76-4063-b957-66ecf1252010" />
<Project Path="RcNetCompatPlugin.vcxproj" Id="6b775e9c-9cb6-45fd-86a0-be948a778969" />
</Folder>
<Folder Name="/third_party/">
<Project Path="../third_party/raw_pdb/build/RawPDB.vcxproj" Id="fbe3dbfa-20a7-4f99-9326-ed82c8b7b910" />
<Project Path="fadec.vcxproj" Id="6a30a4f0-1a8d-4c6e-82d4-0a0d9693aa40" />
<Project Path="qscintilla.vcxproj" Id="f7124b57-7682-4702-b725-4d844dc41ada" />
</Folder>
<Project Path="Reclass.vcxproj" Id="c369f1fe-37c2-4c66-ac6d-ecb2b2b4ad5e" />
</Solution>

211
msvc/Reclass.vcxproj Normal file
View File

@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="18.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{C369F1FE-37C2-4C66-AC6D-ECB2B2B4AD5E}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets;concurrent;network;svg</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets;concurrent;network;svg</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\third_party\fadec\;..\third_party\raw_pdb\src\;..\third_party\qscintilla\src\;..\src\</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>dwmapi.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe
xcopy /Y /I "$(SolutionDir)..\src\examples\*.rcx" "$(SolutionDir)$(Platform)\$(Configuration)\examples\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Link>
<AdditionalDependencies>dwmapi.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<ClCompile>
<AdditionalIncludeDirectories>..\third_party\fadec\;..\third_party\raw_pdb\src\;..\third_party\qscintilla\src\;..\src\</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<PostBuildEvent>
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe
xcopy /Y /I "$(SolutionDir)..\src\examples\*.rcx" "$(SolutionDir)$(Platform)\$(Configuration)\examples\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ClCompile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ClCompile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<QtRcc Include="..\src\resources.qrc" />
</ItemGroup>
<ItemGroup>
<QtUic Include="..\src\processpicker.ui" />
</ItemGroup>
<ItemGroup>
<QtMoc Include="..\src\controller.h" />
<QtMoc Include="..\src\editor.h" />
<QtMoc Include="..\src\mainwindow.h" />
<QtMoc Include="..\src\optionsdialog.h" />
<QtMoc Include="..\src\processpicker.h" />
<QtMoc Include="..\src\scanner.h" />
<QtMoc Include="..\src\scannerpanel.h" />
<QtMoc Include="..\src\titlebar.h" />
<QtMoc Include="..\src\typeselectorpopup.h" />
<QtMoc Include="..\src\imports\import_pdb_dialog.h" />
<QtMoc Include="..\src\mcp\mcp_bridge.h" />
<QtMoc Include="..\src\themes\themeeditor.h" />
<QtMoc Include="..\src\themes\thememanager.h" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\addressparser.h" />
<ClInclude Include="..\src\core.h" />
<ClInclude Include="..\src\disasm.h" />
<QtMoc Include="..\src\dock_tab_buttons.h" />
<ClInclude Include="..\src\generator.h" />
<ClInclude Include="..\src\iplugin.h" />
<ClInclude Include="..\src\pluginmanager.h" />
<ClInclude Include="..\src\providerregistry.h" />
<QtMoc Include="..\src\startpage.h" />
<ClInclude Include="..\src\workspace_model.h" />
<ClInclude Include="..\src\imports\export_reclass_xml.h" />
<ClInclude Include="..\src\imports\import_pdb.h" />
<ClInclude Include="..\src\imports\import_reclass_xml.h" />
<ClInclude Include="..\src\imports\import_source.h" />
<ClInclude Include="..\src\providers\buffer_provider.h" />
<ClInclude Include="..\src\providers\null_provider.h" />
<ClInclude Include="..\src\providers\provider.h" />
<ClInclude Include="..\src\providers\snapshot_provider.h" />
<ClInclude Include="..\src\themes\theme.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\addressparser.cpp" />
<ClCompile Include="..\src\compose.cpp" />
<ClCompile Include="..\src\controller.cpp" />
<ClCompile Include="..\src\disasm.cpp" />
<ClCompile Include="..\src\editor.cpp" />
<ClCompile Include="..\src\format.cpp" />
<ClCompile Include="..\src\generator.cpp" />
<ClCompile Include="..\src\main.cpp">
<DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">input</DynamicSource>
<QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(Filename).moc</QtMocFileName>
<DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource>
<QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(Filename).moc</QtMocFileName>
</ClCompile>
<ClCompile Include="..\src\optionsdialog.cpp" />
<ClCompile Include="..\src\pluginmanager.cpp" />
<ClCompile Include="..\src\processpicker.cpp" />
<ClCompile Include="..\src\providerregistry.cpp" />
<ClCompile Include="..\src\scanner.cpp" />
<ClCompile Include="..\src\scannerpanel.cpp" />
<ClCompile Include="..\src\titlebar.cpp" />
<ClCompile Include="..\src\typeselectorpopup.cpp" />
<ClCompile Include="..\src\imports\export_reclass_xml.cpp" />
<ClCompile Include="..\src\imports\import_pdb.cpp" />
<ClCompile Include="..\src\imports\import_pdb_dialog.cpp" />
<ClCompile Include="..\src\imports\import_reclass_xml.cpp" />
<ClCompile Include="..\src\imports\import_source.cpp" />
<ClCompile Include="..\src\mcp\mcp_bridge.cpp" />
<ClCompile Include="..\src\themes\theme.cpp" />
<ClCompile Include="..\src\themes\themeeditor.cpp" />
<ClCompile Include="..\src\themes\thememanager.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="fadec.vcxproj">
<Project>{6A30A4F0-1A8D-4C6E-82D4-0A0D9693AA40}</Project>
</ProjectReference>
<ProjectReference Include="qscintilla.vcxproj">
<Project>{F7124B57-7682-4702-B725-4D844DC41ADA}</Project>
</ProjectReference>
<ProjectReference Include="..\third_party\raw_pdb\build\RawPDB.vcxproj">
<Project>{fbe3dbfa-20a7-4f99-9326-ed82c8b7b910}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Source Files\imports">
<UniqueIdentifier>{A1B2C3D4-0001-0001-0001-000000000001}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\mcp">
<UniqueIdentifier>{A1B2C3D4-0001-0001-0001-000000000002}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\themes">
<UniqueIdentifier>{A1B2C3D4-0001-0001-0001-000000000003}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Header Files\imports">
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000001}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\mcp">
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000002}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\providers">
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000003}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\themes">
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000004}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Form Files">
<UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier>
<Extensions>ui</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<QtRcc Include="..\src\resources.qrc">
<Filter>Resource Files</Filter>
</QtRcc>
</ItemGroup>
<ItemGroup>
<QtUic Include="..\src\processpicker.ui">
<Filter>Form Files</Filter>
</QtUic>
</ItemGroup>
<ItemGroup>
<QtMoc Include="..\src\controller.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\editor.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\mainwindow.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\optionsdialog.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\processpicker.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\scanner.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\scannerpanel.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\titlebar.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\typeselectorpopup.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\imports\import_pdb_dialog.h">
<Filter>Header Files\imports</Filter>
</QtMoc>
<QtMoc Include="..\src\mcp\mcp_bridge.h">
<Filter>Header Files\mcp</Filter>
</QtMoc>
<QtMoc Include="..\src\themes\themeeditor.h">
<Filter>Header Files\themes</Filter>
</QtMoc>
<QtMoc Include="..\src\themes\thememanager.h">
<Filter>Header Files\themes</Filter>
</QtMoc>
<QtMoc Include="..\src\dock_tab_buttons.h">
<Filter>Header Files</Filter>
</QtMoc>
<QtMoc Include="..\src\startpage.h">
<Filter>Header Files</Filter>
</QtMoc>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\addressparser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\core.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\disasm.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\generator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\iplugin.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\pluginmanager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\providerregistry.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\workspace_model.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\imports\export_reclass_xml.h">
<Filter>Header Files\imports</Filter>
</ClInclude>
<ClInclude Include="..\src\imports\import_pdb.h">
<Filter>Header Files\imports</Filter>
</ClInclude>
<ClInclude Include="..\src\imports\import_reclass_xml.h">
<Filter>Header Files\imports</Filter>
</ClInclude>
<ClInclude Include="..\src\imports\import_source.h">
<Filter>Header Files\imports</Filter>
</ClInclude>
<ClInclude Include="..\src\providers\buffer_provider.h">
<Filter>Header Files\providers</Filter>
</ClInclude>
<ClInclude Include="..\src\providers\null_provider.h">
<Filter>Header Files\providers</Filter>
</ClInclude>
<ClInclude Include="..\src\providers\provider.h">
<Filter>Header Files\providers</Filter>
</ClInclude>
<ClInclude Include="..\src\providers\snapshot_provider.h">
<Filter>Header Files\providers</Filter>
</ClInclude>
<ClInclude Include="..\src\themes\theme.h">
<Filter>Header Files\themes</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\addressparser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\compose.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\controller.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\disasm.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\editor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\format.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\generator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\optionsdialog.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\pluginmanager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\processpicker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\providerregistry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\scanner.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\scannerpanel.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\titlebar.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\typeselectorpopup.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\imports\export_reclass_xml.cpp">
<Filter>Source Files\imports</Filter>
</ClCompile>
<ClCompile Include="..\src\imports\import_pdb.cpp">
<Filter>Source Files\imports</Filter>
</ClCompile>
<ClCompile Include="..\src\imports\import_pdb_dialog.cpp">
<Filter>Source Files\imports</Filter>
</ClCompile>
<ClCompile Include="..\src\imports\import_reclass_xml.cpp">
<Filter>Source Files\imports</Filter>
</ClCompile>
<ClCompile Include="..\src\imports\import_source.cpp">
<Filter>Source Files\imports</Filter>
</ClCompile>
<ClCompile Include="..\src\mcp\mcp_bridge.cpp">
<Filter>Source Files\mcp</Filter>
</ClCompile>
<ClCompile Include="..\src\themes\theme.cpp">
<Filter>Source Files\themes</Filter>
</ClCompile>
<ClCompile Include="..\src\themes\themeeditor.cpp">
<Filter>Source Files\themes</Filter>
</ClCompile>
<ClCompile Include="..\src\themes\thememanager.cpp">
<Filter>Source Files\themes</Filter>
</ClCompile>
<ClCompile Include="..\src\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{39E2DDF6-CB76-4063-B957-66ECF1252010}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<RootNamespace>RemoteProcessMemoryPlugin</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\Plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;..\plugins\RemoteProcessMemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>psapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;..\plugins\RemoteProcessMemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>psapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<QtMoc Include="..\src\processpicker.h" />
</ItemGroup>
<ItemGroup>
<QtUic Include="..\src\processpicker.ui" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\plugins\RemoteProcessMemory\RemoteProcessMemoryPlugin.h" />
<ClInclude Include="..\plugins\RemoteProcessMemory\rcx_rpc_protocol.h" />
<ClInclude Include="..\src\iplugin.h" />
<ClInclude Include="..\src\providers\provider.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\plugins\RemoteProcessMemory\RemoteProcessMemoryPlugin.cpp" />
<ClCompile Include="..\src\processpicker.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets" />
</Project>

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{E25D358E-20F0-448B-BB2F-55E9D1F8E7CA}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<RootNamespace>WinDbgMemoryPlugin</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\Plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>dbgeng.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>dbgeng.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<QtMoc Include="..\plugins\WinDbgMemory\WinDbgMemoryPlugin.h" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\iplugin.h" />
<ClInclude Include="..\src\providers\provider.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\plugins\WinDbgMemory\WinDbgMemoryPlugin.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets" />
</Project>

81
msvc/fadec.vcxproj Normal file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6A30A4F0-1A8D-4C6E-82D4-0A0D9693AA40}</ProjectGuid>
<RootNamespace>fadec</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<FadecDir>..\third_party\fadec\</FadecDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(FadecDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4018;4146;4244;4245;4267;4310</DisableSpecificWarnings>
<LanguageStandard_C>stdc11</LanguageStandard_C>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(FadecDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4018;4146;4244;4245;4267;4310</DisableSpecificWarnings>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<LanguageStandard_C>stdc11</LanguageStandard_C>
</ClCompile>
</ItemDefinitionGroup>
<Target Name="GenerateFadecTables" BeforeTargets="ClCompile"
Inputs="$(FadecDir)instrs.txt;$(FadecDir)parseinstrs.py"
Outputs="$(FadecDir)fadec-decode-public.inc;$(FadecDir)fadec-decode-private.inc">
<Exec Command="python &quot;$(FadecDir)parseinstrs.py&quot; decode &quot;$(FadecDir)instrs.txt&quot; &quot;$(FadecDir)fadec-decode-public.inc&quot; &quot;$(FadecDir)fadec-decode-private.inc&quot; --32 --64" />
</Target>
<ItemGroup>
<ClCompile Include="..\third_party\fadec\decode.c" />
<ClCompile Include="..\third_party\fadec\format.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\third_party\fadec\fadec.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>

445
msvc/qscintilla.vcxproj Normal file
View File

@@ -0,0 +1,445 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{F7124B57-7682-4702-B725-4D844DC41ADA}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<RootNamespace>qscintilla</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets;printsupport</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
<QtModules>core;gui;widgets;printsupport</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<QScintillaDir>..\third_party\qscintilla\</QScintillaDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(QScintillaDir)src;$(QScintillaDir)scintilla\include;$(QScintillaDir)scintilla\lexlib;$(QScintillaDir)scintilla\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<PreprocessorDefinitions>SCINTILLA_QT;SCI_LEXER;INCLUDE_DEPRECATED_FEATURES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>Sync</ExceptionHandling>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(QScintillaDir)src;$(QScintillaDir)scintilla\include;$(QScintillaDir)scintilla\lexlib;$(QScintillaDir)scintilla\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<PreprocessorDefinitions>SCINTILLA_QT;SCI_LEXER;INCLUDE_DEPRECATED_FEATURES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>Sync</ExceptionHandling>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
</ItemDefinitionGroup>
<!-- QtMoc headers (Q_OBJECT) — QScintilla Qt wrapper -->
<ItemGroup>
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qsciabstractapis.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qsciapis.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexer.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerasm.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexeravs.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerbash.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerbatch.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexercmake.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexercoffeescript.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexercpp.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexercsharp.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexercss.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexercustom.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerd.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerdiff.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexeredifact.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerfortran.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerfortran77.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerhex.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerhtml.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexeridl.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerintelhex.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerjava.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerjavascript.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerjson.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerlua.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexermakefile.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexermarkdown.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexermasm.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexermatlab.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexernasm.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexeroctave.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerpascal.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerperl.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerpostscript.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerpo.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerpov.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerproperties.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerpython.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerruby.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerspice.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexersql.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexersrec.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexertcl.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexertekhex.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexertex.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerverilog.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexervhdl.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexerxml.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscilexeryaml.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qscimacro.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qsciscintilla.h" />
<QtMoc Include="..\third_party\qscintilla\src\Qsci\qsciscintillabase.h" />
<QtMoc Include="..\third_party\qscintilla\src\SciClasses.h" />
<QtMoc Include="..\third_party\qscintilla\src\ScintillaQt.h" />
</ItemGroup>
<!-- ClInclude headers (no Q_OBJECT) -->
<ItemGroup>
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qscicommand.h" />
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qscicommandset.h" />
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qscidocument.h" />
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qsciglobal.h" />
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qsciprinter.h" />
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qscistyle.h" />
<ClInclude Include="..\third_party\qscintilla\src\Qsci\qscistyledtext.h" />
<ClInclude Include="..\third_party\qscintilla\src\ListBoxQt.h" />
<ClInclude Include="..\third_party\qscintilla\src\SciAccessibility.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\ILexer.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\ILoader.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\Platform.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\Sci_Position.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\SciLexer.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\Scintilla.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\include\ScintillaWidget.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\Accessor.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\CharacterCategory.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\CharacterSet.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\DefaultLexer.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\LexAccessor.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\LexerBase.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\LexerModule.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\LexerNoExceptions.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\LexerSimple.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\OptionSet.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\PropSetSimple.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\SparseState.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\StringCopy.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\StyleContext.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\SubStyles.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\lexlib\WordList.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\AutoComplete.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\CallTip.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\CaseConvert.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\CaseFolder.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Catalogue.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\CellBuffer.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\CharClassify.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\ContractionState.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\DBCS.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Decoration.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Document.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\EditModel.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Editor.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\EditView.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\ElapsedPeriod.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\ExternalLexer.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\FontQuality.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Indicator.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\IntegerRectangle.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\KeyMap.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\LineMarker.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\MarginView.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Partitioning.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\PerLine.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Position.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\PositionCache.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\RESearch.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\RunStyles.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\ScintillaBase.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Selection.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\SparseVector.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\SplitVector.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\Style.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\UniConversion.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\UniqueString.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\ViewStyle.h" />
<ClInclude Include="..\third_party\qscintilla\scintilla\src\XPM.h" />
</ItemGroup>
<!-- QScintilla Qt wrapper sources -->
<ItemGroup>
<ClCompile Include="..\third_party\qscintilla\src\qsciscintilla.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qsciscintillabase.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qsciabstractapis.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qsciapis.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscicommand.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscicommandset.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscidocument.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexer.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerasm.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexeravs.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerbash.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerbatch.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexercmake.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexercoffeescript.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexercpp.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexercsharp.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexercss.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexercustom.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerd.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerdiff.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexeredifact.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerfortran.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerfortran77.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerhex.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerhtml.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexeridl.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerintelhex.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerjava.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerjavascript.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerjson.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerlua.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexermakefile.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexermarkdown.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexermasm.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexermatlab.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexernasm.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexeroctave.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerpascal.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerperl.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerpostscript.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerpo.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerpov.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerproperties.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerpython.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerruby.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerspice.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexersql.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexersrec.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexertcl.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexertekhex.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexertex.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerverilog.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexervhdl.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexerxml.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscilexeryaml.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscimacro.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qsciprinter.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscistyle.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\qscistyledtext.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\InputMethod.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\ListBoxQt.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\PlatQt.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\SciAccessibility.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\SciClasses.cpp" />
<ClCompile Include="..\third_party\qscintilla\src\ScintillaQt.cpp" />
</ItemGroup>
<!-- Scintilla lexers -->
<ItemGroup>
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexA68K.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAPDL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexASY.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAU3.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAVE.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAVS.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAbaqus.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAda.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAsm.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexAsn1.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexBaan.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexBash.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexBasic.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexBatch.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexBibTeX.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexBullant.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCLW.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCOBOL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCPP.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCSS.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCaml.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCmake.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCoffeeScript.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexConf.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCrontab.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexCsound.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexD.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexDMAP.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexDMIS.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexDiff.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexECL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexEDIFACT.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexEScript.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexEiffel.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexErlang.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexErrorList.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexFlagship.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexForth.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexFortran.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexGAP.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexGui4Cli.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexHTML.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexHaskell.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexHex.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexIndent.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexInno.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexJSON.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexKVIrc.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexKix.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexLaTeX.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexLisp.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexLout.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexLua.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMMIXAL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMPT.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMSSQL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMagik.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMake.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMarkdown.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMatlab.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMaxima.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMetapost.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexModula.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexMySQL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexNimrod.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexNsis.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexNull.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexOScript.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexOpal.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPB.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPLM.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPO.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPOV.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPS.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPascal.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPerl.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPowerPro.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPowerShell.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexProgress.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexProps.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexPython.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexR.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexRebol.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexRegistry.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexRuby.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexRust.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSAS.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSML.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSQL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSTTXT.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexScriptol.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSmalltalk.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSorcus.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSpecman.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexSpice.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexStata.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTACL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTADS3.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTAL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTCL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTCMD.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTeX.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexTxt2tags.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexVB.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexVHDL.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexVerilog.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexVisualProlog.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexers\LexYAML.cpp" />
</ItemGroup>
<!-- Scintilla lexlib -->
<ItemGroup>
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\Accessor.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\CharacterCategory.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\CharacterSet.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\DefaultLexer.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\LexerBase.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\LexerModule.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\LexerNoExceptions.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\LexerSimple.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\PropSetSimple.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\StyleContext.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\lexlib\WordList.cpp" />
</ItemGroup>
<!-- Scintilla core engine -->
<ItemGroup>
<ClCompile Include="..\third_party\qscintilla\scintilla\src\AutoComplete.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\CallTip.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\CaseConvert.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\CaseFolder.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Catalogue.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\CellBuffer.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\CharClassify.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\ContractionState.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\DBCS.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Decoration.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Document.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\EditModel.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Editor.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\EditView.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\ExternalLexer.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Indicator.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\KeyMap.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\LineMarker.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\MarginView.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\PerLine.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\PositionCache.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\RESearch.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\RunStyles.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\ScintillaBase.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Selection.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\Style.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\UniConversion.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\ViewStyle.cpp" />
<ClCompile Include="..\third_party\qscintilla\scintilla\src\XPM.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets" />
</Project>

View File

@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.20)
project(KernelMemoryPlugin LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Qt is found by the parent project; QT variable (Qt5 or Qt6) is inherited
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC OFF) # run uic manually to avoid dupbuild with ProcessMemoryPlugin
# ─── Generate ui_processpicker.h in our own build dir ────────────────
set(_UI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui")
set(_UI_HDR "${CMAKE_CURRENT_BINARY_DIR}/ui_processpicker.h")
add_custom_command(
OUTPUT "${_UI_HDR}"
COMMAND ${QT}::uic -o "${_UI_HDR}" "${_UI_SRC}"
DEPENDS "${_UI_SRC}"
COMMENT "UIC processpicker.ui (KernelMemoryPlugin)"
VERBATIM
)
# ─── Plugin DLL ──────────────────────────────────────────────────────
set(PLUGIN_SOURCES
KernelMemoryPlugin.h
KernelMemoryPlugin.cpp
rcx_drv_protocol.h
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
"${_UI_HDR}"
)
add_library(KernelMemoryPlugin SHARED ${PLUGIN_SOURCES})
target_link_libraries(KernelMemoryPlugin PRIVATE
${QT}::Widgets
${_QT_WINEXTRAS}
)
if(WIN32)
target_link_libraries(KernelMemoryPlugin PRIVATE psapi shell32 advapi32)
endif()
if(UNIX AND NOT APPLE)
target_compile_options(KernelMemoryPlugin PRIVATE -fvisibility=hidden)
endif()
target_include_directories(KernelMemoryPlugin PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../../src
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR} # for ui_processpicker.h
)
set_target_properties(KernelMemoryPlugin PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
)
install(TARGETS KernelMemoryPlugin
LIBRARY DESTINATION Plugins
RUNTIME DESTINATION Plugins
)

View File

@@ -0,0 +1,751 @@
#include "KernelMemoryPlugin.h"
#include "../../src/processpicker.h"
#include <QStyle>
#include <QApplication>
#include <QMenu>
#include <QMessageBox>
#include <QDir>
#include <QFileInfo>
#include <QGuiApplication>
#include <QLibrary>
#ifdef _WIN32
#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <shellapi.h>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtWin>
#endif
#endif
// ─────────────────────────────────────────────────────────────────────────
// Helper: DeviceIoControl wrapper
// ─────────────────────────────────────────────────────────────────────────
#ifdef _WIN32
static bool ioctlCall(HANDLE h, DWORD code,
const void* in, DWORD inLen,
void* out, DWORD outLen,
DWORD* bytesReturned = nullptr)
{
DWORD br = 0;
BOOL ok = DeviceIoControl(h, code, const_cast<LPVOID>(in), inLen,
out, outLen, &br, nullptr);
if (bytesReturned) *bytesReturned = br;
return ok != FALSE;
}
#endif // _WIN32
// ─────────────────────────────────────────────────────────────────────────
// KernelProcessProvider
// ─────────────────────────────────────────────────────────────────────────
KernelProcessProvider::KernelProcessProvider(void* driverHandle, uint32_t pid, const QString& processName)
: m_driverHandle(driverHandle)
, m_pid(pid)
, m_processName(processName)
{
if (m_driverHandle) {
queryPeb();
cacheModules();
}
}
bool KernelProcessProvider::read(uint64_t addr, void* buf, int len) const
{
#ifdef _WIN32
if (!m_driverHandle || len <= 0) return false;
if (len > RCX_DRV_MAX_VIRTUAL) len = RCX_DRV_MAX_VIRTUAL;
RcxDrvReadRequest req{};
req.pid = m_pid;
req.address = addr;
req.length = (uint32_t)len;
DWORD br = 0;
BOOL ok = DeviceIoControl((HANDLE)m_driverHandle,
IOCTL_RCX_READ_MEMORY,
&req, sizeof(req),
buf, (DWORD)len, &br, nullptr);
// Zero unread portion (partial copy)
if ((int)br < len)
memset((char*)buf + br, 0, len - br);
return ok || br > 0;
#else
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
#endif
}
int KernelProcessProvider::size() const
{
return m_driverHandle ? 0x10000 : 0;
}
bool KernelProcessProvider::write(uint64_t addr, const void* buf, int len)
{
#ifdef _WIN32
if (!m_driverHandle || len <= 0) return false;
if (len > RCX_DRV_MAX_VIRTUAL) return false;
// Build request: header + inline data
QByteArray packet(sizeof(RcxDrvWriteRequest) + len, Qt::Uninitialized);
auto* req = reinterpret_cast<RcxDrvWriteRequest*>(packet.data());
req->pid = m_pid;
req->_pad0 = 0;
req->address = addr;
req->length = (uint32_t)len;
req->_pad1 = 0;
memcpy(packet.data() + sizeof(RcxDrvWriteRequest), buf, len);
return ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_WRITE_MEMORY,
packet.constData(), (DWORD)packet.size(),
nullptr, 0);
#else
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
#endif
}
QString KernelProcessProvider::getSymbol(uint64_t addr) const
{
for (const auto& mod : m_modules) {
if (addr >= mod.base && addr < mod.base + mod.size) {
uint64_t offset = addr - mod.base;
return QStringLiteral("%1+0x%2")
.arg(mod.name)
.arg(offset, 0, 16, QChar('0'));
}
}
return {};
}
uint64_t KernelProcessProvider::symbolToAddress(const QString& name) const
{
for (const auto& mod : m_modules) {
if (mod.name.compare(name, Qt::CaseInsensitive) == 0)
return mod.base;
}
return 0;
}
QVector<rcx::MemoryRegion> KernelProcessProvider::enumerateRegions() const
{
QVector<rcx::MemoryRegion> regions;
#ifdef _WIN32
if (!m_driverHandle) return regions;
RcxDrvQueryRegionsRequest req{};
req.pid = m_pid;
// Allocate generous output buffer for region entries
constexpr int kMaxEntries = 8192;
QByteArray outBuf(kMaxEntries * sizeof(RcxDrvRegionEntry), Qt::Uninitialized);
DWORD br = 0;
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_REGIONS,
&req, sizeof(req),
outBuf.data(), (DWORD)outBuf.size(), &br))
return regions;
int count = (int)(br / sizeof(RcxDrvRegionEntry));
auto* entries = reinterpret_cast<const RcxDrvRegionEntry*>(outBuf.constData());
for (int i = 0; i < count; ++i) {
const auto& e = entries[i];
// Only include committed, accessible regions
if (!(e.state & 0x1000)) continue; // MEM_COMMIT = 0x1000
uint32_t p = e.protect;
if (p & 0x01) continue; // PAGE_NOACCESS
if (p & 0x100) continue; // PAGE_GUARD
rcx::MemoryRegion region;
region.base = e.base;
region.size = e.size;
region.readable = true;
region.writable = (p & 0x04) || (p & 0x08) || (p & 0x40) || (p & 0x80);
region.executable = (p & 0x10) || (p & 0x20) || (p & 0x40) || (p & 0x80);
// Match module name
for (const auto& mod : m_modules) {
if (region.base >= mod.base && region.base < mod.base + mod.size) {
region.moduleName = mod.name;
break;
}
}
regions.append(region);
}
#endif
return regions;
}
void KernelProcessProvider::queryPeb()
{
#ifdef _WIN32
RcxDrvQueryPebRequest req{};
req.pid = m_pid;
RcxDrvQueryPebResponse resp{};
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_PEB,
&req, sizeof(req), &resp, sizeof(resp))) {
m_peb = resp.pebAddress;
if (resp.pointerSize == 4)
m_pointerSize = 4;
}
#endif
}
QVector<rcx::Provider::ThreadInfo> KernelProcessProvider::tebs() const
{
QVector<ThreadInfo> result;
#ifdef _WIN32
if (!m_driverHandle) return result;
RcxDrvQueryTebsRequest req{};
req.pid = m_pid;
constexpr int kMaxThreads = 4096;
QByteArray outBuf(kMaxThreads * sizeof(RcxDrvTebEntry), Qt::Uninitialized);
DWORD br = 0;
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_TEBS,
&req, sizeof(req),
outBuf.data(), (DWORD)outBuf.size(), &br))
return result;
int count = (int)(br / sizeof(RcxDrvTebEntry));
auto* entries = reinterpret_cast<const RcxDrvTebEntry*>(outBuf.constData());
for (int i = 0; i < count; ++i)
result.push_back(ThreadInfo{entries[i].tebAddress, entries[i].threadId});
#endif
return result;
}
void KernelProcessProvider::cacheModules()
{
#ifdef _WIN32
if (!m_driverHandle) return;
RcxDrvQueryModulesRequest req{};
req.pid = m_pid;
constexpr int kMaxModules = 1024;
QByteArray outBuf(kMaxModules * sizeof(RcxDrvModuleEntry), Qt::Uninitialized);
DWORD br = 0;
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_MODULES,
&req, sizeof(req),
outBuf.data(), (DWORD)outBuf.size(), &br))
return;
int count = (int)(br / sizeof(RcxDrvModuleEntry));
auto* entries = reinterpret_cast<const RcxDrvModuleEntry*>(outBuf.constData());
m_modules.reserve(count);
for (int i = 0; i < count; ++i) {
QString modName = QString::fromUtf16(reinterpret_cast<const char16_t*>(entries[i].name));
if (i == 0)
m_base = entries[i].base;
m_modules.push_back(ModuleInfo{modName, entries[i].base, entries[i].size});
}
#endif
}
// ─────────────────────────────────────────────────────────────────────────
// KernelProcessProvider — paging / address translation
// ─────────────────────────────────────────────────────────────────────────
uint64_t KernelProcessProvider::getCr3() const
{
#ifdef _WIN32
if (m_cr3Cache) return m_cr3Cache;
if (!m_driverHandle) return 0;
RcxDrvReadCr3Request req{};
req.pid = m_pid;
RcxDrvReadCr3Response resp{};
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_READ_CR3,
&req, sizeof(req), &resp, sizeof(resp))) {
m_cr3Cache = resp.cr3;
return m_cr3Cache;
}
#endif
return 0;
}
rcx::VtopResult KernelProcessProvider::translateAddress(uint64_t va) const
{
rcx::VtopResult result{};
#ifdef _WIN32
if (!m_driverHandle) return result;
RcxDrvVtopRequest req{};
req.pid = m_pid;
req.virtualAddress = va;
RcxDrvVtopResponse resp{};
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_VTOP,
&req, sizeof(req), &resp, sizeof(resp))) {
result.physical = resp.physicalAddress;
result.pml4e = resp.pml4e;
result.pdpte = resp.pdpte;
result.pde = resp.pde;
result.pte = resp.pte;
result.pageSize = resp.pageSize;
result.valid = resp.valid != 0;
}
#else
Q_UNUSED(va);
#endif
return result;
}
QVector<uint64_t> KernelProcessProvider::readPageTable(uint64_t physAddr, int startIdx, int count) const
{
QVector<uint64_t> entries;
#ifdef _WIN32
if (!m_driverHandle) return entries;
if (startIdx < 0 || startIdx >= 512) return entries;
if (count <= 0) return entries;
if (startIdx + count > 512) count = 512 - startIdx;
// Read the full 4KB page table via physical read
int byteOffset = startIdx * 8;
int byteLen = count * 8;
QByteArray buf(byteLen, 0);
RcxDrvPhysReadRequest req{};
req.physAddress = physAddr + byteOffset;
req.length = (uint32_t)byteLen;
req.width = 0; // memcpy mode
DWORD br = 0;
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_READ_PHYS,
&req, sizeof(req), buf.data(), (DWORD)byteLen, &br)) {
entries.resize(count);
memcpy(entries.data(), buf.constData(), byteLen);
}
#else
Q_UNUSED(physAddr); Q_UNUSED(startIdx); Q_UNUSED(count);
#endif
return entries;
}
// ─────────────────────────────────────────────────────────────────────────
// KernelPhysProvider
// ─────────────────────────────────────────────────────────────────────────
KernelPhysProvider::KernelPhysProvider(void* driverHandle, uint64_t baseAddr)
: m_driverHandle(driverHandle)
, m_baseAddr(baseAddr)
{
}
bool KernelPhysProvider::read(uint64_t addr, void* buf, int len) const
{
#ifdef _WIN32
if (!m_driverHandle || len <= 0) return false;
// Read in 4KB chunks (driver cap)
int offset = 0;
while (offset < len) {
int chunk = qMin(len - offset, (int)RCX_DRV_MAX_PHYSICAL);
RcxDrvPhysReadRequest req{};
req.physAddress = addr + offset;
req.length = (uint32_t)chunk;
req.width = 0; // memcpy mode
DWORD br = 0;
BOOL ok = DeviceIoControl((HANDLE)m_driverHandle,
IOCTL_RCX_READ_PHYS,
&req, sizeof(req),
(char*)buf + offset, (DWORD)chunk, &br, nullptr);
if (!ok && br == 0) {
memset((char*)buf + offset, 0, len - offset);
return offset > 0;
}
if ((int)br < chunk)
memset((char*)buf + offset + br, 0, chunk - br);
offset += chunk;
}
return true;
#else
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
#endif
}
bool KernelPhysProvider::write(uint64_t addr, const void* buf, int len)
{
#ifdef _WIN32
if (!m_driverHandle || len <= 0) return false;
int offset = 0;
while (offset < len) {
int chunk = qMin(len - offset, (int)RCX_DRV_MAX_PHYSICAL);
QByteArray packet(sizeof(RcxDrvPhysWriteRequest) + chunk, Qt::Uninitialized);
auto* req = reinterpret_cast<RcxDrvPhysWriteRequest*>(packet.data());
req->physAddress = addr + offset;
req->length = (uint32_t)chunk;
req->width = 0;
memcpy(packet.data() + sizeof(RcxDrvPhysWriteRequest), (const char*)buf + offset, chunk);
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_WRITE_PHYS,
packet.constData(), (DWORD)packet.size(),
nullptr, 0))
return false;
offset += chunk;
}
return true;
#else
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
#endif
}
// ─────────────────────────────────────────────────────────────────────────
// KernelMemoryPlugin
// ─────────────────────────────────────────────────────────────────────────
KernelMemoryPlugin::KernelMemoryPlugin()
{
}
KernelMemoryPlugin::~KernelMemoryPlugin()
{
stopDriver();
}
QIcon KernelMemoryPlugin::Icon() const
{
return qApp->style()->standardIcon(QStyle::SP_DriveHDIcon);
}
bool KernelMemoryPlugin::canHandle(const QString& target) const
{
return target.startsWith(QStringLiteral("km:"))
|| target.startsWith(QStringLiteral("phys:"));
}
std::unique_ptr<rcx::Provider> KernelMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
{
if (!ensureDriverLoaded(errorMsg))
return nullptr;
#ifdef _WIN32
if (target.startsWith(QStringLiteral("km:"))) {
// km:{pid}:{name}
QStringList parts = target.mid(3).split(':');
bool ok = false;
uint32_t pid = parts[0].toUInt(&ok);
if (!ok || pid == 0) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target: ") + target;
return nullptr;
}
QString name = parts.size() > 1 ? parts[1] : QStringLiteral("PID %1").arg(pid);
auto prov = std::make_unique<KernelProcessProvider>((void*)m_driverHandle, pid, name);
if (!prov->isValid()) {
if (errorMsg)
*errorMsg = QStringLiteral("Failed to read process %1 (PID: %2) via kernel driver.")
.arg(name).arg(pid);
return nullptr;
}
return prov;
}
if (target.startsWith(QStringLiteral("phys:"))) {
// phys:{baseAddr}
bool ok = false;
uint64_t baseAddr = target.mid(5).toULongLong(&ok, 16);
if (!ok) baseAddr = 0;
return std::make_unique<KernelPhysProvider>((void*)m_driverHandle, baseAddr);
}
#endif
if (errorMsg) *errorMsg = QStringLiteral("Unknown target format: ") + target;
return nullptr;
}
uint64_t KernelMemoryPlugin::getInitialBaseAddress(const QString& target) const
{
if (target.startsWith(QStringLiteral("phys:"))) {
bool ok = false;
uint64_t addr = target.mid(5).toULongLong(&ok, 16);
return ok ? addr : 0;
}
// For process mode, the provider discovers base via modules
return 0;
}
bool KernelMemoryPlugin::selectTarget(QWidget* parent, QString* target)
{
// Show process picker directly (physical memory is accessed via
// context menu "Browse Page Tables" / "Follow Physical Frame" on an
// attached kernel process).
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
QList<ProcessInfo> processes;
for (const auto& pinfo : pluginProcesses) {
ProcessInfo info;
info.pid = pinfo.pid;
info.name = pinfo.name;
info.path = pinfo.path;
info.icon = pinfo.icon;
info.is32Bit = pinfo.is32Bit;
processes.append(info);
}
ProcessPicker picker(processes, parent);
if (picker.exec() == QDialog::Accepted) {
uint32_t pid = picker.selectedProcessId();
QString name = picker.selectedProcessName();
*target = QStringLiteral("km:%1:%2").arg(pid).arg(name);
return true;
}
return false;
}
QVector<PluginProcessInfo> KernelMemoryPlugin::enumerateProcesses()
{
QVector<PluginProcessInfo> processes;
#ifdef _WIN32
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return processes;
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
PluginProcessInfo info;
info.pid = entry.th32ProcessID;
info.name = QString::fromWCharArray(entry.szExeFile);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
if (hProcess) {
wchar_t path[MAX_PATH * 2];
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) {
info.path = QString::fromWCharArray(path);
SHFILEINFOW sfi = {};
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
if (sfi.hIcon) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
#else
QPixmap pixmap = QtWin::fromHICON(sfi.hIcon);
#endif
info.icon = QIcon(pixmap);
DestroyIcon(sfi.hIcon);
}
}
}
BOOL isWow64 = FALSE;
if (IsWow64Process(hProcess, &isWow64) && isWow64)
info.is32Bit = true;
CloseHandle(hProcess);
}
processes.append(info);
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
#endif
return processes;
}
void KernelMemoryPlugin::populatePluginMenu(QMenu* menu)
{
if (!m_driverLoaded) return;
menu->addAction(QStringLiteral("Unload Kernel Driver"), [this]() { unloadDriver(); });
}
// ─────────────────────────────────────────────────────────────────────────
// Driver service management
// ─────────────────────────────────────────────────────────────────────────
QString KernelMemoryPlugin::driverPath() const
{
// Resolve rcxdrv.sys next to the plugin DLL
QString pluginDir = QCoreApplication::applicationDirPath() + QStringLiteral("/Plugins");
return pluginDir + QStringLiteral("/rcxdrv.sys");
}
bool KernelMemoryPlugin::ensureDriverLoaded(QString* errorMsg)
{
#ifdef _WIN32
// Already connected?
if (m_driverLoaded && m_driverHandle != INVALID_HANDLE_VALUE) {
RcxDrvPingResponse ping{};
if (ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping)))
return true;
// Handle went stale — close it and try to reconnect
CloseHandle(m_driverHandle);
m_driverHandle = INVALID_HANDLE_VALUE;
m_driverLoaded = false;
}
// Show wait cursor (SCM + StartService can take seconds on first load)
struct WaitCursorGuard {
WaitCursorGuard() { QGuiApplication::setOverrideCursor(Qt::WaitCursor); }
~WaitCursorGuard() { QGuiApplication::restoreOverrideCursor(); }
} waitCursor;
// Fast path: driver may already be running (previous session, or after disconnect).
// Just try to open the device handle directly.
m_driverHandle = CreateFileA(RCX_DRV_USERMODE_PATH,
GENERIC_READ | GENERIC_WRITE,
0, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (m_driverHandle != INVALID_HANDLE_VALUE) {
RcxDrvPingResponse ping{};
if (ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping))) {
m_driverLoaded = true;
return true;
}
CloseHandle(m_driverHandle);
m_driverHandle = INVALID_HANDLE_VALUE;
}
// Slow path: need to install/start the service.
QString sysPath = driverPath();
if (!QFileInfo::exists(sysPath)) {
if (errorMsg)
*errorMsg = QStringLiteral("Driver not found: %1\n\n"
"Place rcxdrv.sys in the Plugins folder next to the plugin DLL.").arg(sysPath);
return false;
}
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
if (!scm) {
if (errorMsg)
*errorMsg = QStringLiteral("Failed to open Service Control Manager.\n"
"Run Reclass as Administrator to load the kernel driver.");
return false;
}
// Try to open existing service first
SC_HANDLE svc = OpenServiceW(scm, L"RcxDrv", SERVICE_ALL_ACCESS);
if (!svc) {
// Service doesn't exist — create it
std::wstring wPath = sysPath.toStdWString();
svc = CreateServiceW(scm, L"RcxDrv", L"RcxDrv",
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
wPath.c_str(),
nullptr, nullptr, nullptr, nullptr, nullptr);
if (!svc) {
DWORD err = GetLastError();
if (errorMsg)
*errorMsg = QStringLiteral("Failed to create driver service (error %1).\n"
"Ensure test signing is enabled: bcdedit /set testsigning on").arg(err);
CloseServiceHandle(scm);
return false;
}
}
// Start service (ERROR_SERVICE_ALREADY_RUNNING is fine — means it's already up)
if (!StartServiceW(svc, 0, nullptr)) {
DWORD err = GetLastError();
if (err != ERROR_SERVICE_ALREADY_RUNNING) {
if (errorMsg)
*errorMsg = QStringLiteral("Failed to start driver (error %1).\n"
"Ensure test signing is enabled and the driver is properly signed.").arg(err);
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return false;
}
}
// Done with SCM — don't hold handles open
CloseServiceHandle(svc);
CloseServiceHandle(scm);
// Open device handle
m_driverHandle = CreateFileA(RCX_DRV_USERMODE_PATH,
GENERIC_READ | GENERIC_WRITE,
0, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (m_driverHandle == INVALID_HANDLE_VALUE) {
if (errorMsg)
*errorMsg = QStringLiteral("Driver started but could not open device handle.\n"
"Device path: %1").arg(QString::fromLatin1(RCX_DRV_USERMODE_PATH));
return false;
}
// Verify with ping
RcxDrvPingResponse ping{};
if (!ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping))) {
if (errorMsg)
*errorMsg = QStringLiteral("Driver opened but ping failed.");
CloseHandle(m_driverHandle);
m_driverHandle = INVALID_HANDLE_VALUE;
return false;
}
m_driverLoaded = true;
return true;
#else
if (errorMsg)
*errorMsg = QStringLiteral("Kernel driver is only supported on Windows.");
return false;
#endif
}
void KernelMemoryPlugin::unloadDriver()
{
#ifdef _WIN32
// Close device handle only — service stays running so we can reconnect
if (m_driverHandle != INVALID_HANDLE_VALUE) {
CloseHandle(m_driverHandle);
m_driverHandle = INVALID_HANDLE_VALUE;
}
m_driverLoaded = false;
#endif
}
void KernelMemoryPlugin::stopDriver()
{
#ifdef _WIN32
unloadDriver();
// Full cleanup: stop + delete the service
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
if (scm) {
SC_HANDLE svc = OpenServiceW(scm, L"RcxDrv", SERVICE_ALL_ACCESS);
if (svc) {
SERVICE_STATUS ss;
ControlService(svc, SERVICE_CONTROL_STOP, &ss);
DeleteService(svc);
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
}
#endif
}
// ─────────────────────────────────────────────────────────────────────────
// Plugin factory
// ─────────────────────────────────────────────────────────────────────────
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
{
return new KernelMemoryPlugin();
}

View File

@@ -0,0 +1,142 @@
#pragma once
#include "../../src/iplugin.h"
#include "../../src/core.h"
#include "rcx_drv_protocol.h"
#include <cstdint>
#ifdef _WIN32
#include <windows.h>
#endif
// ─────────────────────────────────────────────────────────────────────────
// Provider variants
// ─────────────────────────────────────────────────────────────────────────
/**
* Kernel-mode process memory provider.
* Reads/writes target process virtual memory via IOCTL_RCX_READ/WRITE_MEMORY.
*/
class KernelProcessProvider : public rcx::Provider
{
public:
KernelProcessProvider(void* driverHandle, uint32_t pid, const QString& processName);
~KernelProcessProvider() override = default;
bool read(uint64_t addr, void* buf, int len) const override;
int size() const override;
bool write(uint64_t addr, const void* buf, int len) override;
bool isWritable() const override { return true; }
QString name() const override { return m_processName; }
QString kind() const override { return QStringLiteral("KernelProcess"); }
QString getSymbol(uint64_t addr) const override;
uint64_t symbolToAddress(const QString& name) const override;
bool isLive() const override { return true; }
uint64_t base() const override { return m_base; }
int pointerSize() const override { return m_pointerSize; }
QVector<rcx::MemoryRegion> enumerateRegions() const override;
bool isReadable(uint64_t, int len) const override { return m_driverHandle && len >= 0; }
uint32_t pid() const { return m_pid; }
uint64_t peb() const override { return m_peb; }
QVector<ThreadInfo> tebs() const override;
// ── Paging / address translation ──
bool hasKernelPaging() const override { return true; }
uint64_t getCr3() const override;
rcx::VtopResult translateAddress(uint64_t va) const override;
QVector<uint64_t> readPageTable(uint64_t physAddr, int startIdx = 0, int count = 512) const override;
void* driverHandle() const { return m_driverHandle; }
private:
void queryPeb();
void cacheModules();
void* m_driverHandle;
uint32_t m_pid;
QString m_processName;
uint64_t m_base = 0;
int m_pointerSize = 8;
uint64_t m_peb = 0;
mutable uint64_t m_cr3Cache = 0;
struct ModuleInfo {
QString name;
uint64_t base;
uint64_t size;
};
QVector<ModuleInfo> m_modules;
};
/**
* Kernel-mode physical memory provider.
* Reads/writes raw physical addresses via IOCTL_RCX_READ/WRITE_PHYS.
*/
class KernelPhysProvider : public rcx::Provider
{
public:
KernelPhysProvider(void* driverHandle, uint64_t baseAddr);
~KernelPhysProvider() override = default;
bool read(uint64_t addr, void* buf, int len) const override;
int size() const override { return m_driverHandle ? 0x10000 : 0; }
bool write(uint64_t addr, const void* buf, int len) override;
bool isWritable() const override { return true; }
QString name() const override { return QStringLiteral("Physical Memory"); }
QString kind() const override { return QStringLiteral("Physical"); }
bool isLive() const override { return true; }
uint64_t base() const override { return m_baseAddr; }
bool isReadable(uint64_t, int len) const override { return m_driverHandle && len >= 0; }
void setBaseAddr(uint64_t addr) { m_baseAddr = addr; }
void* driverHandle() const { return m_driverHandle; }
private:
void* m_driverHandle;
uint64_t m_baseAddr;
};
// ─────────────────────────────────────────────────────────────────────────
// Plugin
// ─────────────────────────────────────────────────────────────────────────
class KernelMemoryPlugin : public IProviderPlugin
{
public:
KernelMemoryPlugin();
~KernelMemoryPlugin() override;
std::string Name() const override { return "Kernel Memory"; }
std::string Version() const override { return "1.0.0"; }
std::string Author() const override { return "Reclass"; }
std::string Description() const override { return "Read and write memory via kernel driver (IOCTL)"; }
k_ELoadType LoadType() const override { return k_ELoadTypeManual; }
QIcon Icon() const override;
bool canHandle(const QString& target) const override;
std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg) override;
uint64_t getInitialBaseAddress(const QString& target) const override;
bool selectTarget(QWidget* parent, QString* target) override;
bool providesProcessList() const override { return true; }
QVector<PluginProcessInfo> enumerateProcesses() override;
void populatePluginMenu(QMenu* menu) override;
private:
bool ensureDriverLoaded(QString* errorMsg = nullptr);
void unloadDriver(); // close handle only — service stays running
void stopDriver(); // full cleanup: close handle + stop + delete service
QString driverPath() const;
#ifdef _WIN32
HANDLE m_driverHandle = INVALID_HANDLE_VALUE;
#endif
bool m_driverLoaded = false;
};
// Plugin export
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();

View File

@@ -0,0 +1,99 @@
@echo off
setlocal enabledelayedexpansion
:: ── Auto-detect MSVC (override with MSVC env var) ──
if not defined MSVC (
set "VSBASE=C:\Program Files\Microsoft Visual Studio\2022"
for %%E in (Enterprise Professional Community BuildTools) do (
if exist "!VSBASE!\%%E\VC\Tools\MSVC" (
for /f "delims=" %%V in ('dir /b /ad /o-n "!VSBASE!\%%E\VC\Tools\MSVC" 2^>nul') do (
if not defined MSVC set "MSVC=!VSBASE!\%%E\VC\Tools\MSVC\%%V"
)
)
)
)
if not defined MSVC (
echo ERROR: Could not find MSVC toolchain
exit /b 1
)
:: ── Auto-detect WDK (override with WDK_INC_ROOT and WDK_LIB_ROOT env vars) ──
:: SDK_INC_ROOT is optional; when WDK is installed traditionally, SDK shared
:: headers live alongside WDK headers. NuGet splits them into a separate package.
if not defined WDK_INC_ROOT (
set "WDK=C:\Program Files (x86)\Windows Kits\10"
set WDKVER=
for /f "delims=" %%V in ('dir /b /ad /o-n "!WDK!\Include" 2^>nul') do (
if exist "!WDK!\Include\%%V\km\ntddk.h" (
if not defined WDKVER set "WDKVER=%%V"
)
)
if not defined WDKVER (
echo ERROR: Could not find WDK headers under !WDK!\Include
echo Set WDK_INC_ROOT and WDK_LIB_ROOT environment variables to override.
exit /b 1
)
set "WDK_INC_ROOT=!WDK!\Include\!WDKVER!"
set "WDK_LIB_ROOT=!WDK!\Lib\!WDKVER!"
set "SDK_INC_ROOT=!WDK!\Include\!WDKVER!"
)
:: If SDK_INC_ROOT not set, default to WDK_INC_ROOT (traditional install has both)
if not defined SDK_INC_ROOT set "SDK_INC_ROOT=%WDK_INC_ROOT%"
echo Using MSVC: %MSVC%
echo Using WDK inc: %WDK_INC_ROOT%
echo Using SDK inc: %SDK_INC_ROOT%
echo Using WDK lib: %WDK_LIB_ROOT%
set "CL_EXE=%MSVC%\bin\Hostx64\x64\cl.exe"
set "LINK_EXE=%MSVC%\bin\Hostx64\x64\link.exe"
set "SRCDIR=%~dp0"
set "OUTDIR=%SRCDIR%build"
if not exist "%OUTDIR%" mkdir "%OUTDIR%"
echo === Compiling rcxdrv.c ===
"%CL_EXE%" /nologo /c /Zi /W4 /WX- /O2 /GS- ^
/D "NDEBUG" /D "_AMD64_" /D "AMD64" /D "_WIN64" /D "KERNEL" ^
/D "NTDDI_VERSION=0x0A000000" ^
/I "%WDK_INC_ROOT%\km" ^
/I "%WDK_INC_ROOT%\km\crt" ^
/I "%WDK_INC_ROOT%\shared" ^
/I "%SDK_INC_ROOT%\shared" ^
/I "%SDK_INC_ROOT%\ucrt" ^
/kernel ^
/Fo"%OUTDIR%\rcxdrv.obj" ^
"%SRCDIR%rcxdrv.c"
if errorlevel 1 goto :fail
echo === Linking rcxdrv.sys ===
"%LINK_EXE%" /nologo ^
/OUT:"%OUTDIR%\rcxdrv.sys" ^
/DRIVER:WDM ^
/SUBSYSTEM:NATIVE ^
/ENTRY:DriverEntry ^
/MACHINE:X64 ^
/NODEFAULTLIB ^
/RELEASE ^
/MERGE:.rdata=.text ^
/INTEGRITYCHECK ^
/PDBALTPATH:rcxdrv.pdb ^
/PDB:"%OUTDIR%\rcxdrv.pdb" ^
"%OUTDIR%\rcxdrv.obj" ^
"%WDK_LIB_ROOT%\km\x64\ntoskrnl.lib" ^
"%WDK_LIB_ROOT%\km\x64\hal.lib" ^
"%WDK_LIB_ROOT%\km\x64\BufferOverflowK.lib" ^
"%MSVC%\lib\x64\libcmt.lib"
if errorlevel 1 goto :fail
echo.
echo === SUCCESS ===
echo Output: %OUTDIR%\rcxdrv.sys
goto :eof
:fail
echo.
echo === BUILD FAILED ===
exit /b 1

View File

@@ -0,0 +1,808 @@
/*
* rcxdrv.c -- Minimal kernel-mode memory driver for Reclass.
*
* Provides: virtual memory R/W (per-process), physical memory R/W,
* region/PEB/module/TEB query, CR3 read, virtual-to-physical translation.
*
* Safety: all inputs validated, SEH around privileged instructions,
* MmCopyVirtualMemory for cross-process reads (no attach deadlock),
* METHOD_BUFFERED (no raw user pointers).
*/
#include <ntifs.h>
#include "../rcx_drv_protocol.h"
/* ── Undocumented but stable kernel exports (Vista+) ────────────── */
NTSTATUS NTAPI MmCopyVirtualMemory(
PEPROCESS SourceProcess, PVOID SourceAddress,
PEPROCESS TargetProcess, PVOID TargetAddress,
SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode,
PSIZE_T ReturnSize);
PPEB NTAPI PsGetProcessPeb(PEPROCESS Process);
PVOID NTAPI PsGetProcessWow64Process(PEPROCESS Process);
PVOID NTAPI PsGetThreadTeb(PETHREAD Thread);
/*
* PsGetNextProcessThread is undocumented (not in any .lib).
* We resolve it dynamically via MmGetSystemRoutineAddress.
*/
typedef PETHREAD (NTAPI *PsGetNextProcessThread_t)(PEPROCESS Process, PETHREAD Thread);
static PsGetNextProcessThread_t g_PsGetNextProcessThread = NULL;
/* ── Manual structure definitions (kernel-mode) ─────────────────── */
/* These are partially opaque in WDK headers; define just the offsets we need. */
typedef struct _MEMORY_BASIC_INFORMATION_KM {
PVOID BaseAddress;
PVOID AllocationBase;
ULONG AllocationProtect;
SIZE_T RegionSize;
ULONG State;
ULONG Protect;
ULONG Type;
} MEMORY_BASIC_INFORMATION_KM;
#define MEM_COMMIT_KM 0x1000
/* PEB.Ldr minimal definition for module enumeration */
typedef struct _PEB_LDR_DATA_KM {
UCHAR Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InLoadOrderModuleList;
} PEB_LDR_DATA_KM;
/* PEB minimal: only need Ldr at offset 0x18 (x64) */
typedef struct _PEB_KM {
UCHAR Reserved1[2];
UCHAR BeingDebugged;
UCHAR Reserved2[0x15];
PEB_LDR_DATA_KM* Ldr; /* offset 0x18 on x64 */
} PEB_KM;
/* LDR_DATA_TABLE_ENTRY minimal for walking InLoadOrderModuleList */
typedef struct _LDR_DATA_TABLE_ENTRY_KM {
LIST_ENTRY InLoadOrderLinks; /* offset 0x00 */
LIST_ENTRY InMemoryOrderLinks; /* offset 0x10 */
LIST_ENTRY InInitializationOrderLinks; /* offset 0x20 */
PVOID DllBase; /* offset 0x30 */
PVOID EntryPoint; /* offset 0x38 */
ULONG SizeOfImage; /* offset 0x40 */
ULONG _pad;
UNICODE_STRING FullDllName; /* offset 0x48 */
UNICODE_STRING BaseDllName; /* offset 0x58 */
} LDR_DATA_TABLE_ENTRY_KM;
/* ── Forward declarations ────────────────────────────────────────── */
static NTSTATUS DispatchCreateClose(PDEVICE_OBJECT dev, PIRP irp);
static NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp);
DRIVER_UNLOAD DriverUnload;
/* ZwCurrentProcess() macro for ZwQueryVirtualMemory */
#ifndef ZwCurrentProcess
#define ZwCurrentProcess() ((HANDLE)(LONG_PTR)-1)
#endif
/* ── Helpers ─────────────────────────────────────────────────────── */
#define VALIDATE_INPUT(irp, stk, T) \
do { \
if ((stk)->Parameters.DeviceIoControl.InputBufferLength < sizeof(T)) { \
(irp)->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; \
(irp)->IoStatus.Information = 0; \
IoCompleteRequest((irp), IO_NO_INCREMENT); \
return STATUS_BUFFER_TOO_SMALL; \
} \
} while (0)
#define VALIDATE_OUTPUT(irp, stk, minSize) \
do { \
if ((stk)->Parameters.DeviceIoControl.OutputBufferLength < (ULONG)(minSize)) { \
(irp)->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; \
(irp)->IoStatus.Information = 0; \
IoCompleteRequest((irp), IO_NO_INCREMENT); \
return STATUS_BUFFER_TOO_SMALL; \
} \
} while (0)
static NTSTATUS LookupProcess(ULONG pid, PEPROCESS* proc)
{
return PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)pid, proc);
}
/* ── Safe physical mapping (MDL-based, avoids MmMapIoSpace BSOD) ── */
/*
* MmMapIoSpace/MmUnmapIoSpace BSODs (bugcheck 0x50 in
* MiClearMappingAndDereferenceIoSpace) when used on RAM-backed physical
* addresses. MDL-based mapping is safe for both RAM and MMIO.
*
* CRITICAL: cacheType must match the existing kernel mapping of the page.
* Use MmCached for RAM pages (already mapped cached by the kernel).
* Use MmNonCached ONLY for MMIO/device registers.
* Mismatched cache attributes (e.g. MmNonCached on RAM) cause silent
* kernel memory corruption via CPU cache coherency conflicts.
*/
typedef struct { PMDL mdl; PVOID base; } PHYS_MAP_CTX;
static PVOID MapPhysical(uint64_t physAddr, SIZE_T size,
MEMORY_CACHING_TYPE cacheType, PHYS_MAP_CTX* ctx)
{
ctx->mdl = NULL;
ctx->base = NULL;
ULONG_PTR pageOff = (ULONG_PTR)(physAddr & (PAGE_SIZE - 1));
SIZE_T totalSize = pageOff + size;
ULONG pages = (ULONG)((totalSize + PAGE_SIZE - 1) / PAGE_SIZE);
PMDL mdl = IoAllocateMdl(NULL, (ULONG)totalSize, FALSE, FALSE, NULL);
if (!mdl) return NULL;
PPFN_NUMBER pfn = MmGetMdlPfnArray(mdl);
PFN_NUMBER startPfn = (PFN_NUMBER)(physAddr / PAGE_SIZE);
for (ULONG i = 0; i < pages; i++)
pfn[i] = startPfn + i;
mdl->MdlFlags |= MDL_PAGES_LOCKED;
__try {
ctx->base = MmMapLockedPagesSpecifyCache(
mdl, KernelMode, cacheType, NULL, FALSE, NormalPagePriority);
} __except (EXCEPTION_EXECUTE_HANDLER) {
IoFreeMdl(mdl);
return NULL;
}
if (!ctx->base) { IoFreeMdl(mdl); return NULL; }
ctx->mdl = mdl;
return (PUCHAR)ctx->base + pageOff;
}
static void UnmapPhysical(PHYS_MAP_CTX* ctx)
{
if (ctx->base) MmUnmapLockedPages(ctx->base, ctx->mdl);
if (ctx->mdl) IoFreeMdl(ctx->mdl);
ctx->base = NULL;
ctx->mdl = NULL;
}
/* ── Virtual memory read ─────────────────────────────────────────── */
static NTSTATUS HandleReadMemory(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvReadRequest);
struct RcxDrvReadRequest* req = (struct RcxDrvReadRequest*)irp->AssociatedIrp.SystemBuffer;
if (req->length == 0 || req->length > RCX_DRV_MAX_VIRTUAL)
return STATUS_INVALID_PARAMETER;
VALIDATE_OUTPUT(irp, stk, req->length);
/* Save request fields before MmCopyVirtualMemory overwrites SystemBuffer.
* METHOD_BUFFERED aliases input and output to the same buffer, so the
* copy destination (SystemBuffer) clobbers req->* fields. */
ULONG pid = req->pid;
uint64_t address = req->address;
ULONG length = req->length;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(pid, &proc);
if (!NT_SUCCESS(st)) return st;
SIZE_T bytesRead = 0;
st = MmCopyVirtualMemory(
proc, (PVOID)address,
PsGetCurrentProcess(), irp->AssociatedIrp.SystemBuffer,
(SIZE_T)length, KernelMode, &bytesRead);
ObDereferenceObject(proc);
/* Partial reads: zero remainder, report success */
if (st == STATUS_PARTIAL_COPY) {
RtlZeroMemory((PUCHAR)irp->AssociatedIrp.SystemBuffer + bytesRead,
length - bytesRead);
irp->IoStatus.Information = length;
return STATUS_SUCCESS;
}
irp->IoStatus.Information = NT_SUCCESS(st) ? length : 0;
return st;
}
/* ── Virtual memory write ────────────────────────────────────────── */
static NTSTATUS HandleWriteMemory(PIRP irp, PIO_STACK_LOCATION stk)
{
ULONG inputLen = stk->Parameters.DeviceIoControl.InputBufferLength;
if (inputLen < sizeof(struct RcxDrvWriteRequest))
return STATUS_BUFFER_TOO_SMALL;
struct RcxDrvWriteRequest* req = (struct RcxDrvWriteRequest*)irp->AssociatedIrp.SystemBuffer;
if (req->length == 0 || req->length > RCX_DRV_MAX_VIRTUAL)
return STATUS_INVALID_PARAMETER;
if (inputLen < sizeof(struct RcxDrvWriteRequest) + req->length)
return STATUS_BUFFER_TOO_SMALL;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
PUCHAR data = (PUCHAR)req + sizeof(struct RcxDrvWriteRequest);
SIZE_T bytesWritten = 0;
st = MmCopyVirtualMemory(
PsGetCurrentProcess(), data,
proc, (PVOID)req->address,
(SIZE_T)req->length, KernelMode, &bytesWritten);
ObDereferenceObject(proc);
irp->IoStatus.Information = 0;
return st;
}
/* ── Physical memory read ────────────────────────────────────────── */
static NTSTATUS HandleReadPhys(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvPhysReadRequest);
struct RcxDrvPhysReadRequest* req = (struct RcxDrvPhysReadRequest*)irp->AssociatedIrp.SystemBuffer;
if (req->length == 0 || req->length > RCX_DRV_MAX_PHYSICAL)
return STATUS_INVALID_PARAMETER;
if (req->width != 0 && req->width != 1 && req->width != 2 && req->width != 4)
return STATUS_INVALID_PARAMETER;
VALIDATE_OUTPUT(irp, stk, req->length);
/* Save request fields before SystemBuffer is overwritten (METHOD_BUFFERED
* aliases input and output to the same buffer). */
uint64_t physAddress = req->physAddress;
ULONG length = req->length;
ULONG width = req->width;
PUCHAR dst = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
if (width == 0) {
/* Byte copy -- use MmCopyMemory (safe for both RAM and MMIO) */
MM_COPY_ADDRESS srcAddr;
srcAddr.PhysicalAddress.QuadPart = (LONGLONG)physAddress;
SIZE_T bytesCopied = 0;
NTSTATUS st = MmCopyMemory(dst, srcAddr, (SIZE_T)length,
MM_COPY_MEMORY_PHYSICAL, &bytesCopied);
if (!NT_SUCCESS(st)) return st;
if (bytesCopied < length)
RtlZeroMemory(dst + bytesCopied, length - bytesCopied);
irp->IoStatus.Information = length;
return STATUS_SUCCESS;
}
/* Width-aware MMIO reads -- map via MDL (safe for all physical addresses).
* Use MmNonCached: width>0 implies MMIO register access where uncached
* semantics are required for correct device interaction. */
PHYS_MAP_CTX mapCtx;
PUCHAR src = (PUCHAR)MapPhysical(physAddress, (SIZE_T)length, MmNonCached, &mapCtx);
if (!src) return STATUS_UNSUCCESSFUL;
__try {
ULONG off = 0;
while (off + width <= length) {
if (width == 1)
dst[off] = READ_REGISTER_UCHAR(&src[off]);
else if (width == 2)
*(USHORT*)(dst + off) = READ_REGISTER_USHORT((PUSHORT)(src + off));
else
*(ULONG*)(dst + off) = READ_REGISTER_ULONG((PULONG)(src + off));
off += width;
}
if (off < length)
RtlZeroMemory(dst + off, length - off);
} __except (EXCEPTION_EXECUTE_HANDLER) {
UnmapPhysical(&mapCtx);
return STATUS_UNSUCCESSFUL;
}
UnmapPhysical(&mapCtx);
irp->IoStatus.Information = length;
return STATUS_SUCCESS;
}
/* ── Physical memory write ───────────────────────────────────────── */
static NTSTATUS HandleWritePhys(PIRP irp, PIO_STACK_LOCATION stk)
{
ULONG inputLen = stk->Parameters.DeviceIoControl.InputBufferLength;
if (inputLen < sizeof(struct RcxDrvPhysWriteRequest))
return STATUS_BUFFER_TOO_SMALL;
struct RcxDrvPhysWriteRequest* req = (struct RcxDrvPhysWriteRequest*)irp->AssociatedIrp.SystemBuffer;
if (req->length == 0 || req->length > RCX_DRV_MAX_PHYSICAL)
return STATUS_INVALID_PARAMETER;
if (req->width != 0 && req->width != 1 && req->width != 2 && req->width != 4)
return STATUS_INVALID_PARAMETER;
if (inputLen < sizeof(struct RcxDrvPhysWriteRequest) + req->length)
return STATUS_BUFFER_TOO_SMALL;
PUCHAR src = (PUCHAR)req + sizeof(struct RcxDrvPhysWriteRequest);
/* Map via MDL (safe for both RAM and MMIO).
* width==0 → RAM byte write (MmCached to avoid cache attribute conflict).
* width>0 → MMIO register write (MmNonCached for correct device semantics). */
MEMORY_CACHING_TYPE ct = (req->width == 0) ? MmCached : MmNonCached;
PHYS_MAP_CTX mapCtx;
PUCHAR dst = (PUCHAR)MapPhysical(req->physAddress, (SIZE_T)req->length, ct, &mapCtx);
if (!dst) return STATUS_UNSUCCESSFUL;
__try {
if (req->width == 0) {
RtlCopyMemory(dst, src, req->length);
} else {
ULONG off = 0;
while (off + req->width <= req->length) {
if (req->width == 1)
WRITE_REGISTER_UCHAR(&dst[off], src[off]);
else if (req->width == 2)
WRITE_REGISTER_USHORT((PUSHORT)(dst + off), *(USHORT*)(src + off));
else
WRITE_REGISTER_ULONG((PULONG)(dst + off), *(ULONG*)(src + off));
off += req->width;
}
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
UnmapPhysical(&mapCtx);
return STATUS_UNSUCCESSFUL;
}
UnmapPhysical(&mapCtx);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
/* ── Ping ────────────────────────────────────────────────────────── */
static NTSTATUS HandlePing(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvPingResponse));
struct RcxDrvPingResponse* rsp = (struct RcxDrvPingResponse*)irp->AssociatedIrp.SystemBuffer;
rsp->version = RCX_DRV_VERSION;
rsp->driverBuild = __LINE__;
irp->IoStatus.Information = sizeof(struct RcxDrvPingResponse);
return STATUS_SUCCESS;
}
/* ── Query PEB ───────────────────────────────────────────────────── */
static NTSTATUS HandleQueryPeb(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryPebRequest);
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvQueryPebResponse));
struct RcxDrvQueryPebRequest* req = (struct RcxDrvQueryPebRequest*)irp->AssociatedIrp.SystemBuffer;
struct RcxDrvQueryPebResponse* rsp = (struct RcxDrvQueryPebResponse*)irp->AssociatedIrp.SystemBuffer;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
rsp->pebAddress = (uint64_t)(ULONG_PTR)PsGetProcessPeb(proc);
rsp->pointerSize = 8;
rsp->_pad = 0;
/* Detect WoW64 (32-bit process on 64-bit OS) */
PVOID wow64 = PsGetProcessWow64Process(proc);
if (wow64) {
rsp->pebAddress = (uint64_t)(ULONG_PTR)wow64;
rsp->pointerSize = 4;
}
ObDereferenceObject(proc);
irp->IoStatus.Information = sizeof(struct RcxDrvQueryPebResponse);
return STATUS_SUCCESS;
}
/* ── Query Regions ───────────────────────────────────────────────── */
static NTSTATUS HandleQueryRegions(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryRegionsRequest);
struct RcxDrvQueryRegionsRequest* req = (struct RcxDrvQueryRegionsRequest*)irp->AssociatedIrp.SystemBuffer;
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
ULONG maxEntries = outputLen / sizeof(struct RcxDrvRegionEntry);
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
/* Attach to target process to query its address space.
* IOCTLs arrive at PASSIVE_LEVEL; KeStackAttachProcess requires <= APC_LEVEL.
* ZwQueryVirtualMemory with ZwCurrentProcess() while attached queries the
* attached process's address space (correct). */
KAPC_STATE apcState;
KeStackAttachProcess(proc, &apcState);
struct RcxDrvRegionEntry* entries = (struct RcxDrvRegionEntry*)irp->AssociatedIrp.SystemBuffer;
ULONG count = 0;
PVOID addr = NULL;
MEMORY_BASIC_INFORMATION_KM mbi;
while (count < maxEntries) {
SIZE_T retLen = 0;
st = ZwQueryVirtualMemory(ZwCurrentProcess(), addr, 0 /*MemoryBasicInformation*/,
&mbi, sizeof(mbi), &retLen);
if (!NT_SUCCESS(st)) break;
if (mbi.State == MEM_COMMIT_KM) {
entries[count].base = (uint64_t)(ULONG_PTR)mbi.BaseAddress;
entries[count].size = (uint64_t)mbi.RegionSize;
entries[count].protect = mbi.Protect;
entries[count].state = mbi.State;
count++;
}
ULONG_PTR next = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
if (next <= (ULONG_PTR)addr) break; /* overflow */
addr = (PVOID)next;
}
KeUnstackDetachProcess(&apcState);
ObDereferenceObject(proc);
irp->IoStatus.Information = count * sizeof(struct RcxDrvRegionEntry);
return STATUS_SUCCESS;
}
/* ── Query Modules ───────────────────────────────────────────────── */
static NTSTATUS HandleQueryModules(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryModulesRequest);
struct RcxDrvQueryModulesRequest* req = (struct RcxDrvQueryModulesRequest*)irp->AssociatedIrp.SystemBuffer;
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
ULONG maxEntries = outputLen / sizeof(struct RcxDrvModuleEntry);
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
/* Attach to target process to read PEB->Ldr */
KAPC_STATE apcState;
KeStackAttachProcess(proc, &apcState);
struct RcxDrvModuleEntry* entries = (struct RcxDrvModuleEntry*)irp->AssociatedIrp.SystemBuffer;
ULONG count = 0;
__try {
/* Read PEB address */
PEB_KM* peb = (PEB_KM*)PsGetProcessPeb(proc);
if (!peb) goto done;
ProbeForRead(peb, sizeof(PEB_KM), 1);
/* PEB->Ldr at offset 0x18 (x64) */
PEB_LDR_DATA_KM* ldr = peb->Ldr;
if (!ldr) goto done;
ProbeForRead(ldr, sizeof(PEB_LDR_DATA_KM), 1);
/* Walk InLoadOrderModuleList */
LIST_ENTRY* head = &ldr->InLoadOrderModuleList;
LIST_ENTRY* cur = head->Flink;
while (cur != head && count < maxEntries) {
LDR_DATA_TABLE_ENTRY_KM* entry = CONTAINING_RECORD(cur, LDR_DATA_TABLE_ENTRY_KM, InLoadOrderLinks);
entries[count].base = (uint64_t)(ULONG_PTR)entry->DllBase;
entries[count].size = (uint64_t)entry->SizeOfImage;
/* Copy wide-char name (truncate to 259 chars + null) */
USHORT nameLen = entry->BaseDllName.Length / sizeof(WCHAR);
if (nameLen > 259) nameLen = 259;
if (entry->BaseDllName.Buffer) {
RtlCopyMemory(entries[count].name, entry->BaseDllName.Buffer,
nameLen * sizeof(uint16_t));
}
entries[count].name[nameLen] = 0;
count++;
cur = cur->Flink;
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
/* Partial results are fine */
}
done:
KeUnstackDetachProcess(&apcState);
ObDereferenceObject(proc);
irp->IoStatus.Information = count * sizeof(struct RcxDrvModuleEntry);
return STATUS_SUCCESS;
}
/* ── Query TEBs ──────────────────────────────────────────────────── */
/*
* Walk the target process's thread list to collect TEB addresses.
* Uses PsGetNextProcessThread (undocumented but stable since Vista).
*/
static NTSTATUS HandleQueryTebs(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryTebsRequest);
struct RcxDrvQueryTebsRequest* req = (struct RcxDrvQueryTebsRequest*)irp->AssociatedIrp.SystemBuffer;
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
ULONG maxEntries = outputLen / sizeof(struct RcxDrvTebEntry);
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
struct RcxDrvTebEntry* entries = (struct RcxDrvTebEntry*)irp->AssociatedIrp.SystemBuffer;
ULONG count = 0;
if (!g_PsGetNextProcessThread) {
ObDereferenceObject(proc);
return STATUS_NOT_SUPPORTED;
}
/* PsGetNextProcessThread increments the ref on the returned PETHREAD and
* dereferences the previous one. We must release the last thread if we
* exit the loop early (exception or maxEntries hit). */
{
PETHREAD thread = NULL;
__try {
while ((thread = g_PsGetNextProcessThread(proc, thread)) != NULL) {
if (count >= maxEntries) {
/* Hit limit — release the thread PsGetNextProcessThread just returned */
ObDereferenceObject(thread);
break;
}
PVOID teb = PsGetThreadTeb(thread);
if (teb) {
entries[count].tebAddress = (uint64_t)(ULONG_PTR)teb;
entries[count].threadId = (uint32_t)(ULONG_PTR)PsGetThreadId(thread);
entries[count]._pad = 0;
count++;
}
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
/* Exception mid-iteration: thread holds a referenced PETHREAD — release it */
if (thread)
ObDereferenceObject(thread);
}
}
ObDereferenceObject(proc);
irp->IoStatus.Information = count * sizeof(struct RcxDrvTebEntry);
return STATUS_SUCCESS;
}
/* ── Read CR3 (DirectoryTableBase) ────────────────────────────────── */
/*
* EPROCESS.DirectoryTableBase offset. Stable across Win10/11 x64.
* Verified: 0x028 on 1507-22H2+ (KPROCESS is at offset 0 of EPROCESS).
*/
#define KPROCESS_DIRECTORY_TABLE_BASE 0x028
static NTSTATUS HandleReadCr3(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvReadCr3Request);
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvReadCr3Response));
struct RcxDrvReadCr3Request* req = (struct RcxDrvReadCr3Request*)irp->AssociatedIrp.SystemBuffer;
struct RcxDrvReadCr3Response* rsp = (struct RcxDrvReadCr3Response*)irp->AssociatedIrp.SystemBuffer;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
__try {
rsp->cr3 = *(uint64_t*)((PUCHAR)proc + KPROCESS_DIRECTORY_TABLE_BASE);
/* Mask off PCID bits (bits 0-11) to get the PML4 physical address */
rsp->cr3 &= ~0xFFFULL;
rsp->kernelCr3 = rsp->cr3; /* same on non-KPTI; KPTI shadow is not easily accessible */
} __except (EXCEPTION_EXECUTE_HANDLER) {
ObDereferenceObject(proc);
return STATUS_UNSUCCESSFUL;
}
ObDereferenceObject(proc);
irp->IoStatus.Information = sizeof(struct RcxDrvReadCr3Response);
return STATUS_SUCCESS;
}
/* ── Virtual-to-Physical address translation ─────────────────────── */
/* NOTE: This walks the page table non-atomically via 4 sequential physical reads.
* The page table can be modified between reads (e.g., page-out, remap). This is
* an inherent limitation shared by WinDbg's !vtop and similar tools. For a
* debugging/reversing tool this tradeoff is acceptable. */
/* Extract physical frame address from a page table entry (bits 51:12) */
#define PTE_FRAME(pte) ((pte) & 0x000FFFFFFFFFF000ULL)
/* Check Present bit (bit 0) */
#define PTE_PRESENT(pte) ((pte) & 1ULL)
/* Check Page Size bit (bit 7) -- indicates large/huge page */
#define PTE_PS(pte) ((pte) & (1ULL << 7))
static NTSTATUS HandleVtop(PIRP irp, PIO_STACK_LOCATION stk)
{
VALIDATE_INPUT(irp, stk, struct RcxDrvVtopRequest);
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvVtopResponse));
struct RcxDrvVtopRequest* req = (struct RcxDrvVtopRequest*)irp->AssociatedIrp.SystemBuffer;
struct RcxDrvVtopResponse* rsp = (struct RcxDrvVtopResponse*)irp->AssociatedIrp.SystemBuffer;
PEPROCESS proc = NULL;
NTSTATUS st = LookupProcess(req->pid, &proc);
if (!NT_SUCCESS(st)) return st;
/* Read CR3 */
uint64_t cr3;
__try {
cr3 = *(uint64_t*)((PUCHAR)proc + KPROCESS_DIRECTORY_TABLE_BASE);
cr3 &= ~0xFFFULL;
} __except (EXCEPTION_EXECUTE_HANDLER) {
ObDereferenceObject(proc);
return STATUS_UNSUCCESSFUL;
}
ObDereferenceObject(proc);
uint64_t va = req->virtualAddress;
RtlZeroMemory(rsp, sizeof(*rsp));
/* Extract indices from virtual address:
* [47:39] = PML4 index, [38:30] = PDPT index,
* [29:21] = PD index, [20:12] = PT index,
* [11:0] = page offset */
ULONG pml4Idx = (ULONG)((va >> 39) & 0x1FF);
ULONG pdptIdx = (ULONG)((va >> 30) & 0x1FF);
ULONG pdIdx = (ULONG)((va >> 21) & 0x1FF);
ULONG ptIdx = (ULONG)((va >> 12) & 0x1FF);
MM_COPY_ADDRESS ca;
SIZE_T copied;
uint64_t entry;
/* Level 4: PML4 -- use MmCopyMemory (safe for RAM, unlike MmMapIoSpace) */
ca.PhysicalAddress.QuadPart = (LONGLONG)(cr3 + pml4Idx * 8);
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
rsp->pml4e = entry;
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
/* Level 3: PDPT */
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + pdptIdx * 8);
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
rsp->pdpte = entry;
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
if (PTE_PS(entry)) {
/* 1GB huge page: physical = frame[51:30] | va[29:0] */
rsp->physicalAddress = (entry & 0x000FFFFFC0000000ULL) | (va & 0x3FFFFFFFULL);
rsp->pageSize = 2;
rsp->valid = 1;
goto done;
}
/* Level 2: PD */
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + pdIdx * 8);
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
rsp->pde = entry;
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
if (PTE_PS(entry)) {
/* 2MB large page: physical = frame[51:21] | va[20:0] */
rsp->physicalAddress = (entry & 0x000FFFFFFFE00000ULL) | (va & 0x1FFFFFULL);
rsp->pageSize = 1;
rsp->valid = 1;
goto done;
}
/* Level 1: PT */
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + ptIdx * 8);
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
rsp->pte = entry;
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
/* 4KB page: physical = frame[51:12] | va[11:0] */
rsp->physicalAddress = PTE_FRAME(entry) | (va & 0xFFFULL);
rsp->pageSize = 0;
rsp->valid = 1;
done:
irp->IoStatus.Information = sizeof(struct RcxDrvVtopResponse);
return STATUS_SUCCESS;
}
/* ── IOCTL dispatch ──────────────────────────────────────────────── */
static NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(irp);
NTSTATUS st;
switch (stk->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_RCX_READ_MEMORY: st = HandleReadMemory(irp, stk); break;
case IOCTL_RCX_WRITE_MEMORY: st = HandleWriteMemory(irp, stk); break;
case IOCTL_RCX_QUERY_REGIONS: st = HandleQueryRegions(irp, stk); break;
case IOCTL_RCX_QUERY_PEB: st = HandleQueryPeb(irp, stk); break;
case IOCTL_RCX_QUERY_MODULES: st = HandleQueryModules(irp, stk); break;
case IOCTL_RCX_QUERY_TEBS: st = HandleQueryTebs(irp, stk); break;
case IOCTL_RCX_PING: st = HandlePing(irp, stk); break;
case IOCTL_RCX_READ_PHYS: st = HandleReadPhys(irp, stk); break;
case IOCTL_RCX_WRITE_PHYS: st = HandleWritePhys(irp, stk); break;
case IOCTL_RCX_READ_CR3: st = HandleReadCr3(irp, stk); break;
case IOCTL_RCX_VTOP: st = HandleVtop(irp, stk); break;
default:
st = STATUS_INVALID_DEVICE_REQUEST;
irp->IoStatus.Information = 0;
break;
}
irp->IoStatus.Status = st;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return st;
}
/* ── Create / Close (permit open/close) ──────────────────────────── */
static NTSTATUS DispatchCreateClose(PDEVICE_OBJECT dev, PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
/* ── Unload ──────────────────────────────────────────────────────── */
void DriverUnload(PDRIVER_OBJECT drv)
{
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\DosDevices\\RcxDrv");
IoDeleteSymbolicLink(&symlink);
if (drv->DeviceObject)
IoDeleteDevice(drv->DeviceObject);
}
/* ── Entry point ─────────────────────────────────────────────────── */
NTSTATUS DriverEntry(PDRIVER_OBJECT drv, PUNICODE_STRING regPath)
{
UNREFERENCED_PARAMETER(regPath);
/* Resolve undocumented APIs */
UNICODE_STRING fnName = RTL_CONSTANT_STRING(L"PsGetNextProcessThread");
g_PsGetNextProcessThread = (PsGetNextProcessThread_t)MmGetSystemRoutineAddress(&fnName);
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\RcxDrv");
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\DosDevices\\RcxDrv");
PDEVICE_OBJECT devObj = NULL;
NTSTATUS st = IoCreateDevice(drv, 0, &devName, FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN, FALSE, &devObj);
if (!NT_SUCCESS(st)) return st;
st = IoCreateSymbolicLink(&symlink, &devName);
if (!NT_SUCCESS(st)) {
IoDeleteDevice(devObj);
return st;
}
drv->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
drv->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
drv->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
drv->DriverUnload = DriverUnload;
devObj->Flags |= DO_BUFFERED_IO;
devObj->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}

View File

@@ -0,0 +1,17 @@
obj-m += rcxkm.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
install:
insmod rcxkm.ko
uninstall:
rmmod rcxkm
.PHONY: all clean install uninstall

View File

@@ -0,0 +1,132 @@
/*
* rcxkm.c -- Linux kernel module stub for Reclass kernel memory provider.
*
* Provides /dev/rcxkm char device with ioctl() dispatch using the same
* protocol structs as the Windows driver (rcx_drv_protocol.h).
*
* Build: make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
*
* TODO: implement handlers (currently returns -ENOSYS for all IOCTLs).
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/mm.h>
#include "../rcx_drv_protocol.h"
#define DEVICE_NAME "rcxkm"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Reclass");
MODULE_DESCRIPTION("Reclass kernel memory provider (stub)");
/* ── IOCTL dispatch ─────────────────────────────────────────────────── */
static long rcxkm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
(void)filp;
(void)arg;
switch (cmd) {
case IOCTL_RCX_READ_MEMORY:
/* TODO: find_get_pid(pid) -> get_task_struct -> access_process_vm() */
return -ENOSYS;
case IOCTL_RCX_WRITE_MEMORY:
/* TODO: access_process_vm() with FOLL_WRITE */
return -ENOSYS;
case IOCTL_RCX_QUERY_REGIONS:
/* TODO: walk target mm->mmap via VMA iteration */
return -ENOSYS;
case IOCTL_RCX_QUERY_PEB:
/* N/A on Linux (no PEB); could return mm->start_brk or similar */
return -ENOSYS;
case IOCTL_RCX_QUERY_MODULES:
/* TODO: walk target /proc/pid/maps or mm VMAs */
return -ENOSYS;
case IOCTL_RCX_QUERY_TEBS:
/* N/A on Linux (no TEB) */
return -ENOSYS;
case IOCTL_RCX_PING: {
struct RcxDrvPingResponse resp = {
.version = RCX_DRV_VERSION,
.driverBuild = 1,
};
if (copy_to_user((void __user *)arg, &resp, sizeof(resp)))
return -EFAULT;
return 0;
}
case IOCTL_RCX_READ_PHYS:
/* TODO: ioremap() + memcpy_fromio() */
return -ENOSYS;
case IOCTL_RCX_WRITE_PHYS:
/* TODO: ioremap() + memcpy_toio() */
return -ENOSYS;
default:
return -EINVAL;
}
}
/* ── File operations ────────────────────────────────────────────────── */
static int rcxkm_open(struct inode *inode, struct file *filp)
{
(void)inode; (void)filp;
return 0;
}
static int rcxkm_release(struct inode *inode, struct file *filp)
{
(void)inode; (void)filp;
return 0;
}
static const struct file_operations rcxkm_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rcxkm_ioctl,
.open = rcxkm_open,
.release = rcxkm_release,
};
static struct miscdevice rcxkm_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &rcxkm_fops,
};
/* ── Module init/exit ───────────────────────────────────────────────── */
static int __init rcxkm_init(void)
{
int ret = misc_register(&rcxkm_device);
if (ret) {
pr_err("rcxkm: failed to register misc device (err=%d)\n", ret);
return ret;
}
pr_info("rcxkm: loaded, device /dev/%s\n", DEVICE_NAME);
return 0;
}
static void __exit rcxkm_exit(void)
{
misc_deregister(&rcxkm_device);
pr_info("rcxkm: unloaded\n");
}
module_init(rcxkm_init);
module_exit(rcxkm_exit);

View File

@@ -0,0 +1,189 @@
/*
* RCX Driver Protocol -- shared between kernel driver and usermode plugin.
* No dependencies beyond standard C headers. Pure C, no Windows types.
*/
#pragma once
#ifdef KERNEL
/* Kernel mode build: avoid stdint.h (not in WDK km/crt) */
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __int64 uint64_t;
typedef signed __int64 int64_t;
#else
#include <stdint.h>
#endif
/* ── Device / service names ───────────────────────────────────────── */
#define RCX_DRV_DEVICE_NAME L"\\Device\\RcxDrv"
#define RCX_DRV_SYMLINK_NAME L"\\DosDevices\\RcxDrv"
#define RCX_DRV_USERMODE_PATH "\\\\.\\RcxDrv"
#define RCX_DRV_SERVICE_NAME "RcxDrv"
/* ── Protocol version ─────────────────────────────────────────────── */
#define RCX_DRV_VERSION 1
/* ── Size limits ──────────────────────────────────────────────────── */
#define RCX_DRV_MAX_VIRTUAL (1024 * 1024) /* 1 MB per virtual read/write */
#define RCX_DRV_MAX_PHYSICAL 4096 /* 4 KB per physical read/write */
/* ── IOCTL codes ──────────────────────────────────────────────────── */
/* CTL_CODE(FILE_DEVICE_UNKNOWN=0x22, function, METHOD_BUFFERED=0, FILE_ANY_ACCESS=0) */
/* Virtual memory (per-process) */
#define IOCTL_RCX_READ_MEMORY 0x222000 /* function 0x800 */
#define IOCTL_RCX_WRITE_MEMORY 0x222004 /* function 0x801 */
#define IOCTL_RCX_QUERY_REGIONS 0x222008 /* function 0x802 */
#define IOCTL_RCX_QUERY_PEB 0x22200C /* function 0x803 */
#define IOCTL_RCX_QUERY_MODULES 0x222010 /* function 0x804 */
#define IOCTL_RCX_QUERY_TEBS 0x222014 /* function 0x805 */
#define IOCTL_RCX_PING 0x222018 /* function 0x806 */
/* Physical memory (MMIO) */
#define IOCTL_RCX_READ_PHYS 0x22201C /* function 0x807 */
#define IOCTL_RCX_WRITE_PHYS 0x222020 /* function 0x808 */
/* Paging / address translation */
#define IOCTL_RCX_READ_CR3 0x222044 /* function 0x811 */
#define IOCTL_RCX_VTOP 0x222048 /* function 0x812 */
/* ── Request / Response structures ────────────────────────────────── */
/* All structs are naturally aligned. Padding fields are explicit. */
/* -- Virtual memory -- */
struct RcxDrvReadRequest {
uint32_t pid;
uint32_t _pad0;
uint64_t address;
uint32_t length; /* max RCX_DRV_MAX_VIRTUAL */
uint32_t _pad1;
};
/* Write: input = header + inline data bytes */
struct RcxDrvWriteRequest {
uint32_t pid;
uint32_t _pad0;
uint64_t address;
uint32_t length; /* max RCX_DRV_MAX_VIRTUAL */
uint32_t _pad1;
/* uint8_t data[length] follows */
};
/* -- Region enumeration -- */
struct RcxDrvQueryRegionsRequest {
uint32_t pid;
uint32_t _pad;
};
struct RcxDrvRegionEntry {
uint64_t base;
uint64_t size;
uint32_t protect; /* raw PAGE_* flags */
uint32_t state; /* MEM_COMMIT etc. */
};
/* -- PEB -- */
struct RcxDrvQueryPebRequest {
uint32_t pid;
uint32_t _pad;
};
struct RcxDrvQueryPebResponse {
uint64_t pebAddress;
uint32_t pointerSize; /* 4 or 8 */
uint32_t _pad;
};
/* -- Modules -- */
struct RcxDrvQueryModulesRequest {
uint32_t pid;
uint32_t _pad;
};
struct RcxDrvModuleEntry {
uint64_t base;
uint64_t size;
uint16_t name[260]; /* wide-char, null-terminated */
};
/* -- TEBs -- */
struct RcxDrvQueryTebsRequest {
uint32_t pid;
uint32_t _pad;
};
struct RcxDrvTebEntry {
uint64_t tebAddress;
uint32_t threadId;
uint32_t _pad;
};
/* -- Ping -- */
struct RcxDrvPingResponse {
uint32_t version;
uint32_t driverBuild;
};
/* -- Physical memory -- */
struct RcxDrvPhysReadRequest {
uint64_t physAddress;
uint32_t length; /* max RCX_DRV_MAX_PHYSICAL */
uint32_t width; /* access width: 1, 2, or 4 (0 = memcpy) */
};
struct RcxDrvPhysWriteRequest {
uint64_t physAddress;
uint32_t length; /* max RCX_DRV_MAX_PHYSICAL */
uint32_t width; /* access width: 1, 2, or 4 (0 = memcpy) */
/* uint8_t data[length] follows */
};
/* -- Paging / address translation -- */
struct RcxDrvReadCr3Request {
uint32_t pid;
uint32_t _pad;
};
struct RcxDrvReadCr3Response {
uint64_t cr3; /* DirectoryTableBase (PML4 physical address) */
uint64_t kernelCr3; /* KernelDirectoryTableBase (KPTI shadow) */
};
struct RcxDrvVtopRequest {
uint32_t pid;
uint32_t _pad;
uint64_t virtualAddress;
};
struct RcxDrvVtopResponse {
uint64_t physicalAddress; /* final translated physical address (with page offset) */
uint64_t pml4e; /* raw PML4 entry value */
uint64_t pdpte; /* raw PDPT entry value */
uint64_t pde; /* raw PD entry value */
uint64_t pte; /* raw PT entry value (0 if large/huge page) */
uint8_t pageSize; /* 0=4KB, 1=2MB, 2=1GB */
uint8_t valid; /* 1 if translation succeeded, 0 if not present */
uint8_t _pad2[6];
};
/* ── Compile-time validation ──────────────────────────────────────── */
#ifdef __cplusplus
static_assert(sizeof(RcxDrvReadRequest) == 24, "ReadRequest layout");
static_assert(sizeof(RcxDrvWriteRequest) == 24, "WriteRequest layout");
static_assert(sizeof(RcxDrvRegionEntry) == 24, "RegionEntry layout");
static_assert(sizeof(RcxDrvModuleEntry) == 536, "ModuleEntry layout");
static_assert(sizeof(RcxDrvTebEntry) == 16, "TebEntry layout");
static_assert(sizeof(RcxDrvPingResponse) == 8, "PingResponse layout");
static_assert(sizeof(RcxDrvReadCr3Response) == 16, "ReadCr3Response layout");
static_assert(sizeof(RcxDrvVtopRequest) == 16, "VtopRequest layout");
static_assert(sizeof(RcxDrvVtopResponse) == 48, "VtopResponse layout");
#endif

View File

@@ -19,6 +19,60 @@
#include <tlhelp32.h>
#include <psapi.h>
#include <shellapi.h>
typedef struct _UNICODE_STRING { USHORT Length, MaximumLength; PWSTR Buffer; } UNICODE_STRING;
typedef struct _CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID;
typedef struct _SYSTEM_THREAD_INFORMATION {
LARGE_INTEGER KernelTime, UserTime, CreateTime;
ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId;
LONG Priority, BasePriority; ULONG ContextSwitches, ThreadState, WaitReason;
} SYSTEM_THREAD_INFORMATION;
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset; // 0x000
ULONG NumberOfThreads; // 0x004
LARGE_INTEGER WorkingSetPrivateSize; // 0x008
ULONG HardFaultCount; // 0x010
ULONG NumberOfThreadsHighWatermark; // 0x014
ULONGLONG CycleTime; // 0x018
LARGE_INTEGER CreateTime; // 0x020
LARGE_INTEGER UserTime; // 0x028
LARGE_INTEGER KernelTime; // 0x030
UNICODE_STRING ImageName; // 0x038
LONG BasePriority; // 0x048
HANDLE UniqueProcessId; // 0x050
PVOID InheritedFromUniqueProcessId; // 0x058
ULONG HandleCount; // 0x060
ULONG SessionId; // 0x064
ULONG_PTR UniqueProcessKey; // 0x068
SIZE_T PeakVirtualSize; // 0x070
SIZE_T VirtualSize; // 0x078
ULONG PageFaultCount; // 0x080
ULONG _pad0; // 0x084
SIZE_T PeakWorkingSetSize; // 0x088
SIZE_T WorkingSetSize; // 0x090
SIZE_T QuotaPeakPagedPoolUsage; // 0x098
SIZE_T QuotaPagedPoolUsage; // 0x0A0
SIZE_T QuotaPeakNonPagedPoolUsage; // 0x0A8
SIZE_T QuotaNonPagedPoolUsage; // 0x0B0
SIZE_T PagefileUsage; // 0x0B8
SIZE_T PeakPagefileUsage; // 0x0C0
SIZE_T PrivatePageCount; // 0x0C8
LARGE_INTEGER ReadOperationCount; // 0x0D0
LARGE_INTEGER WriteOperationCount; // 0x0D8
LARGE_INTEGER OtherOperationCount; // 0x0E0
LARGE_INTEGER ReadTransferCount; // 0x0E8
LARGE_INTEGER WriteTransferCount; // 0x0F0
LARGE_INTEGER OtherTransferCount; // 0x0F8
} SYSTEM_PROCESS_INFORMATION; // sizeof = 0x100
typedef struct alignas(8) _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus; // 0x00
ULONG _pad; // 0x04
PVOID TebBaseAddress; // 0x08
CLIENT_ID ClientId; // 0x10
ULONG_PTR AffinityMask; // 0x20
LONG Priority; // 0x28
LONG BasePriority; // 0x2C
} THREAD_BASIC_INFORMATION;
#elif defined(__linux__)
#include <climits>
#include <sys/types.h>
@@ -56,8 +110,24 @@ ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& proces
m_writable = false;
}
if (m_handle)
if (m_handle) {
// Detect 32-bit (WoW64) process
BOOL isWow64 = FALSE;
if (IsWow64Process(m_handle, &isWow64) && isWow64)
m_pointerSize = 4;
// Query PEB address via NtQueryInformationProcess
{
typedef NTSTATUS(NTAPI* NtQIP_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
static NtQIP_t pNtQIP = (NtQIP_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
if (pNtQIP) {
struct { PVOID r1; PVOID PebBaseAddress; PVOID r2[2]; ULONG_PTR pid; PVOID r3; } pbi = {};
ULONG retLen = 0;
if (pNtQIP(m_handle, /*ProcessBasicInformation*/0, &pbi, sizeof(pbi), &retLen) >= 0 && pbi.PebBaseAddress)
m_peb = (uint64_t)(uintptr_t)pbi.PebBaseAddress;
}
}
cacheModules();
}
}
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
@@ -65,7 +135,7 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
if (!m_handle || len <= 0) return false;
SIZE_T bytesRead = 0;
ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead);
ReadProcessMemory(m_handle, (LPCVOID)(addr), buf, (SIZE_T)len, &bytesRead);
if ((int)bytesRead < len)
memset((char*)buf + bytesRead, 0, len - bytesRead);
return bytesRead > 0;
@@ -76,7 +146,7 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
if (!m_handle || !m_writable || len <= 0) return false;
SIZE_T bytesWritten = 0;
if (WriteProcessMemory(m_handle, (LPVOID)(m_base + addr), buf, (SIZE_T)len, &bytesWritten))
if (WriteProcessMemory(m_handle, (LPVOID)(addr), buf, (SIZE_T)len, &bytesWritten))
return bytesWritten == (SIZE_T)len;
return false;
}
@@ -115,8 +185,14 @@ void ProcessMemoryProvider::cacheModules()
if ( i == 0 )
m_base = (uint64_t)mi.lpBaseOfDll;
m_modules.append({
WCHAR modPath[MAX_PATH];
QString fullPath;
if (GetModuleFileNameExW(m_handle, mods[i], modPath, MAX_PATH))
fullPath = QString::fromWCharArray(modPath);
m_modules.push_back(ModuleInfo{
QString::fromWCharArray(modName),
fullPath,
(uint64_t)mi.lpBaseOfDll,
(uint64_t)mi.SizeOfImage
});
@@ -124,6 +200,60 @@ void ProcessMemoryProvider::cacheModules()
}
}
QVector<rcx::Provider::ModuleEntry> ProcessMemoryProvider::enumerateModules() const
{
QVector<ModuleEntry> result;
result.reserve(m_modules.size());
for (const auto& m : m_modules)
result.push_back(ModuleEntry{m.name, m.fullPath, m.base, m.size});
return result;
}
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
{
QVector<rcx::MemoryRegion> regions;
if (!m_handle) return regions;
MEMORY_BASIC_INFORMATION mbi;
uint64_t addr = 0;
while (VirtualQueryEx(m_handle, (LPCVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {
if (mbi.State == MEM_COMMIT &&
!(mbi.Protect & PAGE_NOACCESS) &&
!(mbi.Protect & PAGE_GUARD))
{
rcx::MemoryRegion region;
region.base = (uint64_t)mbi.BaseAddress;
region.size = mbi.RegionSize;
region.readable = true;
region.writable = (mbi.Protect & PAGE_READWRITE) ||
(mbi.Protect & PAGE_WRITECOPY) ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
(mbi.Protect & PAGE_EXECUTE_READ) ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
// Match module name from cached module list
for (const auto& mod : m_modules) {
if (region.base >= mod.base && region.base < mod.base + mod.size) {
region.moduleName = mod.name;
break;
}
}
regions.append(region);
}
uint64_t next = (uint64_t)mbi.BaseAddress + mbi.RegionSize;
if (next <= addr) break; // overflow protection
addr = next;
}
return regions;
}
#elif defined(__linux__)
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
@@ -147,24 +277,33 @@ ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& proces
m_writable = false;
}
if (m_fd >= 0)
if (m_fd >= 0) {
// Detect 32-bit ELF process
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
QByteArray exePathUtf8 = exePath.toUtf8();
int exeFd = ::open(exePathUtf8.constData(), O_RDONLY);
if (exeFd >= 0) {
unsigned char elfClass = 0;
// ELF e_ident[EI_CLASS] is at offset 4
if (::pread(exeFd, &elfClass, 1, 4) == 1 && elfClass == 1) // ELFCLASS32
m_pointerSize = 4;
::close(exeFd);
}
cacheModules();
}
}
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
{
if (m_fd < 0 || len <= 0) return false;
uint64_t absAddr = m_base + addr;
// Try process_vm_readv first (faster, no fd seek contention)
struct iovec local;
local.iov_base = buf;
local.iov_len = static_cast<size_t>(len);
struct iovec remote;
remote.iov_base = reinterpret_cast<void*>(absAddr);
remote.iov_base = reinterpret_cast<void*>(addr);
remote.iov_len = static_cast<size_t>(len);
ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0);
@@ -172,7 +311,7 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
return true;
// Fallback: pread on /proc/<pid>/mem
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
return nread == static_cast<ssize_t>(len);
}
@@ -180,15 +319,13 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
{
if (m_fd < 0 || !m_writable || len <= 0) return false;
uint64_t absAddr = m_base + addr;
// Try process_vm_writev first
struct iovec local;
local.iov_base = const_cast<void*>(buf);
local.iov_len = static_cast<size_t>(len);
struct iovec remote;
remote.iov_base = reinterpret_cast<void*>(absAddr);
remote.iov_base = reinterpret_cast<void*>(addr);
remote.iov_len = static_cast<size_t>(len);
ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0);
@@ -196,7 +333,7 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
return true;
// Fallback: pwrite on /proc/<pid>/mem
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
return nwritten == static_cast<ssize_t>(len);
}
@@ -278,16 +415,78 @@ void ProcessMemoryProvider::cacheModules()
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
{
QFileInfo fi(it.key());
m_modules.append({
m_modules.push_back(ModuleInfo{
fi.fileName(),
it.key(),
it->base,
it->end - it->base
});
}
}
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
{
QVector<rcx::MemoryRegion> regions;
if (m_fd < 0) return regions;
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
std::ifstream mapsFile(mapsPath.toStdString());
if (!mapsFile.is_open()) return regions;
std::string line;
while (std::getline(mapsFile, line)) {
std::istringstream iss(line);
std::string addrRange, perms, offset, dev, inode, pathname;
iss >> addrRange >> perms >> offset >> dev >> inode;
std::getline(iss, pathname);
auto dash = addrRange.find('-');
if (dash == std::string::npos) continue;
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
if (perms.size() < 4) continue;
bool readable = (perms[0] == 'r');
bool writable = (perms[1] == 'w');
bool executable = (perms[2] == 'x');
if (!readable) continue;
rcx::MemoryRegion region;
region.base = addrStart;
region.size = addrEnd - addrStart;
region.readable = readable;
region.writable = writable;
region.executable = executable;
// Extract module name from pathname
size_t start = pathname.find_first_not_of(" \t");
if (start != std::string::npos) {
QString qpath = QString::fromStdString(pathname.substr(start));
if (qpath.startsWith('/') && !qpath.startsWith("/dev/") &&
!qpath.startsWith("/memfd:")) {
QFileInfo fi(qpath);
region.moduleName = fi.fileName();
}
}
regions.append(region);
}
return regions;
}
#endif // platform
uint64_t ProcessMemoryProvider::symbolToAddress(const QString& name) const
{
for (const auto& mod : m_modules) {
if (mod.name.compare(name, Qt::CaseInsensitive) == 0)
return mod.base;
}
return 0;
}
ProcessMemoryProvider::~ProcessMemoryProvider()
{
#ifdef _WIN32
@@ -308,6 +507,58 @@ int ProcessMemoryProvider::size() const
#endif
}
QVector<rcx::Provider::ThreadInfo> ProcessMemoryProvider::tebs() const
{
#ifdef _WIN32
QVector<ThreadInfo> result;
if (!m_handle || !m_peb) return result;
typedef NTSTATUS(NTAPI* NtQSI_t)(ULONG, PVOID, ULONG, PULONG);
typedef NTSTATUS(NTAPI* NtQIT_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
static auto pNtQSI = (NtQSI_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
static auto pNtQIT = (NtQIT_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread");
if (!pNtQSI || !pNtQIT) return result;
// Enumerate threads via SystemProcessInformation (class 5)
ULONG retLen = 0;
ULONG bufSize = 1 << 20;
QByteArray buf(bufSize, 0);
NTSTATUS qsiSt;
for (int attempt = 0; attempt < 8; ++attempt) {
qsiSt = pNtQSI(5, buf.data(), bufSize, &retLen);
if ((uint32_t)qsiSt != 0xC0000004u) break;
bufSize *= 2;
buf.resize(bufSize);
}
if (qsiSt < 0) return result;
// Walk process entries to find ours
auto* proc = (SYSTEM_PROCESS_INFORMATION*)buf.data();
for (;;) {
if ((uintptr_t)proc->UniqueProcessId == m_pid) {
auto* threads = (SYSTEM_THREAD_INFORMATION*)((char*)proc + sizeof(*proc));
for (ULONG i = 0; i < proc->NumberOfThreads; ++i) {
DWORD tid = (DWORD)(uintptr_t)threads[i].ClientId.UniqueThread;
HANDLE hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, tid);
if (!hThread) continue;
THREAD_BASIC_INFORMATION tbi = {};
ULONG tbiLen = 0;
NTSTATUS qitSt = pNtQIT(hThread, 0, &tbi, sizeof(tbi), &tbiLen);
if (qitSt >= 0 && tbi.TebBaseAddress)
result.push_back(ThreadInfo{(uint64_t)(uintptr_t)tbi.TebBaseAddress, tid});
CloseHandle(hThread);
}
break;
}
if (!proc->NextEntryOffset) break;
proc = (SYSTEM_PROCESS_INFORMATION*)((char*)proc + proc->NextEntryOffset);
}
return result;
#else
return {};
#endif
}
// ──────────────────────────────────────────────────────────────────────────
// ProcessMemoryPlugin implementation
// ──────────────────────────────────────────────────────────────────────────
@@ -423,6 +674,7 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
info.name = pinfo.name;
info.path = pinfo.path;
info.icon = pinfo.icon;
info.is32Bit = pinfo.is32Bit;
processes.append(info);
}
@@ -484,6 +736,11 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
}
}
// Detect 32-bit (WoW64) process
BOOL isWow64 = FALSE;
if (IsWow64Process(hProcess, &isWow64) && isWow64)
info.is32Bit = true;
CloseHandle(hProcess);
}
@@ -530,6 +787,16 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
info.name = procName;
info.path = resolvedPath;
info.icon = defaultIcon;
// Detect 32-bit ELF process
int exeFd = ::open(exePath.toUtf8().constData(), O_RDONLY);
if (exeFd >= 0) {
unsigned char elfClass = 0;
if (::pread(exeFd, &elfClass, 1, 4) == 1 && elfClass == 1) // ELFCLASS32
info.is32Bit = true;
::close(exeFd);
}
processes.append(info);
}
#endif

View File

@@ -24,15 +24,26 @@ public:
QString name() const override { return m_processName; }
QString kind() const override { return QStringLiteral("LocalProcess"); }
QString getSymbol(uint64_t addr) const override;
uint64_t symbolToAddress(const QString& name) const override;
bool isLive() const override { return true; }
uint64_t base() const override { return m_base; }
void setBase(uint64_t b) override { m_base = b; }
int pointerSize() const override { return m_pointerSize; }
QVector<rcx::MemoryRegion> enumerateRegions() const override;
bool isReadable(uint64_t, int len) const override {
#ifdef _WIN32
return m_handle && len >= 0;
#elif defined(__linux__)
return m_fd >= 0 && len >= 0;
#endif
}
// Process-specific helpers
uint32_t pid() const { return m_pid; }
uint64_t baseAddress() const { return m_base; }
void refreshModules() { m_modules.clear(); cacheModules(); }
uint64_t peb() const override { return m_peb; }
QVector<ThreadInfo> tebs() const override;
QVector<ModuleEntry> enumerateModules() const override;
private:
void cacheModules();
@@ -47,9 +58,12 @@ private:
QString m_processName;
bool m_writable;
uint64_t m_base;
int m_pointerSize = 8;
uint64_t m_peb = 0;
struct ModuleInfo {
QString name;
QString fullPath;
uint64_t base;
uint64_t size;
};

View File

@@ -33,9 +33,8 @@ bool RcNetCompatProvider::read(uint64_t addr, void* buf, int len) const
if (!m_handle || !m_fns.ReadRemoteMemory || len <= 0)
return false;
uint64_t absAddr = m_base + addr;
return m_fns.ReadRemoteMemory(m_handle,
reinterpret_cast<RC_Pointer>(absAddr),
reinterpret_cast<RC_Pointer>(addr),
static_cast<RC_Pointer>(buf),
0, len);
}
@@ -54,9 +53,8 @@ bool RcNetCompatProvider::write(uint64_t addr, const void* buf, int len)
if (!m_handle || !m_fns.WriteRemoteMemory || len <= 0)
return false;
uint64_t absAddr = m_base + addr;
return m_fns.WriteRemoteMemory(m_handle,
reinterpret_cast<RC_Pointer>(absAddr),
reinterpret_cast<RC_Pointer>(addr),
const_cast<RC_Pointer>(static_cast<const void*>(buf)),
0, len);
}
@@ -76,6 +74,15 @@ QString RcNetCompatProvider::getSymbol(uint64_t addr) const
return {};
}
uint64_t RcNetCompatProvider::symbolToAddress(const QString& name) const
{
for (const auto& mod : m_modules) {
if (mod.name.compare(name, Qt::CaseInsensitive) == 0)
return mod.base;
}
return 0;
}
// -- Module enumeration ---------------------------------------------------
namespace {

View File

@@ -27,8 +27,8 @@ public:
QString kind() const override { return QStringLiteral("RcNet"); }
bool isLive() const override { return true; }
uint64_t base() const override { return m_base; }
void setBase(uint64_t b) override { m_base = b; }
QString getSymbol(uint64_t addr) const override;
uint64_t symbolToAddress(const QString& name) const override;
struct ModuleInfo {
QString name;

View File

@@ -0,0 +1,124 @@
cmake_minimum_required(VERSION 3.20)
project(RemoteProcessMemory LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Qt is found by the parent project; QT variable (Qt5 or Qt6) is inherited
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC OFF) # run uic manually to avoid dupbuild with ProcessMemoryPlugin
# ─── 1. Payload DLL/SO (no Qt, minimal dependencies) ────────────────
add_library(rcx_payload SHARED
payload/rcx_payload.cpp
rcx_rpc_protocol.h
)
set_target_properties(rcx_payload PROPERTIES PREFIX "") # rcx_payload.dll / rcx_payload.so
target_include_directories(rcx_payload PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(WIN32)
target_link_libraries(rcx_payload PRIVATE psapi)
else()
target_link_libraries(rcx_payload PRIVATE pthread rt)
target_compile_options(rcx_payload PRIVATE -fvisibility=hidden)
endif()
# Output payload to Plugins/ (same dir as plugin DLL, discovered at runtime)
set_target_properties(rcx_payload PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
)
# Install rule: copy both DLLs to install Plugins/ folder
install(TARGETS rcx_payload
LIBRARY DESTINATION Plugins
RUNTIME DESTINATION Plugins
)
# ─── 2. Plugin DLL (Qt, implements IProviderPlugin) ──────────────────
# Generate ui_processpicker.h in our own build dir (avoids dupbuild with ProcessMemoryPlugin)
set(_UI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui")
set(_UI_HDR "${CMAKE_CURRENT_BINARY_DIR}/ui_processpicker.h")
add_custom_command(
OUTPUT "${_UI_HDR}"
COMMAND ${QT}::uic -o "${_UI_HDR}" "${_UI_SRC}"
DEPENDS "${_UI_SRC}"
COMMENT "UIC processpicker.ui (RemoteProcessMemory)"
VERBATIM
)
set(PLUGIN_SOURCES
RemoteProcessMemoryPlugin.h
RemoteProcessMemoryPlugin.cpp
rcx_rpc_protocol.h
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
"${_UI_HDR}"
)
add_library(RemoteProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
target_link_libraries(RemoteProcessMemoryPlugin PRIVATE
${QT}::Widgets
${_QT_WINEXTRAS}
)
if(WIN32)
target_link_libraries(RemoteProcessMemoryPlugin PRIVATE psapi shell32)
else()
target_link_libraries(RemoteProcessMemoryPlugin PRIVATE rt dl)
target_compile_options(RemoteProcessMemoryPlugin PRIVATE -fvisibility=hidden)
endif()
target_include_directories(RemoteProcessMemoryPlugin PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../../src
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR} # for ui_processpicker.h
)
set_target_properties(RemoteProcessMemoryPlugin PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
)
install(TARGETS RemoteProcessMemoryPlugin
LIBRARY DESTINATION Plugins
RUNTIME DESTINATION Plugins
)
# Plugin must be able to find the payload at runtime
add_dependencies(RemoteProcessMemoryPlugin rcx_payload)
# ─── 3. Test executables (no Qt) ────────────────────────────────────
# Host: loads payload in-process, exposes test buffer
add_executable(test_rpc_host tests/test_rpc_host.cpp)
target_include_directories(test_rpc_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(WIN32)
target_link_libraries(test_rpc_host PRIVATE psapi)
else()
target_link_libraries(test_rpc_host PRIVATE pthread rt dl)
endif()
set_target_properties(test_rpc_host PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
)
add_dependencies(test_rpc_host rcx_payload)
# Client: connects to host, tests + benchmarks
add_executable(test_rpc_client tests/test_rpc_client.cpp)
target_include_directories(test_rpc_client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(WIN32)
target_link_libraries(test_rpc_client PRIVATE psapi)
else()
target_link_libraries(test_rpc_client PRIVATE pthread rt)
endif()
set_target_properties(test_rpc_client PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
)
add_dependencies(test_rpc_client test_rpc_host)

View File

@@ -0,0 +1,939 @@
#include "RemoteProcessMemoryPlugin.h"
#include "rcx_rpc_protocol.h"
#include "../../src/processpicker.h"
#include <QStyle>
#include <QApplication>
#include <QMessageBox>
#include <QPushButton>
#include <QDir>
#include <QFileInfo>
#include <QPixmap>
#include <QImage>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(_WIN32)
#include <QtWin>
#endif
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <tlhelp32.h>
# include <psapi.h>
# include <shellapi.h>
#else
# include <unistd.h>
# include <fcntl.h>
# include <dlfcn.h>
# include <sys/mman.h>
# include <sys/wait.h>
# include <sys/ptrace.h>
# include <sys/user.h>
# include <semaphore.h>
# include <signal.h>
# include <link.h>
# include <climits>
# include <cstring>
# include <fstream>
# include <sstream>
#endif
/* ══════════════════════════════════════════════════════════════════════
* IPC Client
* ══════════════════════════════════════════════════════════════════════ */
struct IpcClient {
#ifdef _WIN32
HANDLE hShm = nullptr;
HANDLE hReqEvent = nullptr;
HANDLE hRspEvent = nullptr;
#else
int shmFd = -1;
sem_t* reqSem = SEM_FAILED;
sem_t* rspSem = SEM_FAILED;
char shmNameBuf[128] = {};
char reqNameBuf[128] = {};
char rspNameBuf[128] = {};
#endif
void* mappedView = nullptr;
QMutex mutex;
bool connected = false;
RcxRpcHeader* header() const {
return mappedView ? reinterpret_cast<RcxRpcHeader*>(mappedView) : nullptr;
}
~IpcClient() { disconnect(); }
/* ── connect / disconnect ──────────────────────────────────────── */
bool connect(uint32_t pid, int timeoutMs = 5000)
{
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32
/* poll for shared memory to appear (payload creating it) */
auto deadline = GetTickCount64() + (uint64_t)timeoutMs;
while (!(hShm = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shmName))) {
if (GetTickCount64() >= deadline) return false;
Sleep(10);
}
mappedView = MapViewOfFile(hShm, FILE_MAP_ALL_ACCESS, 0, 0, RCX_RPC_SHM_SIZE);
if (!mappedView) { CloseHandle(hShm); hShm = nullptr; return false; }
hReqEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, reqName);
hRspEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, rspName);
if (!hReqEvent || !hRspEvent) { disconnect(); return false; }
#else
strncpy(shmNameBuf, shmName, sizeof(shmNameBuf) - 1);
strncpy(reqNameBuf, reqName, sizeof(reqNameBuf) - 1);
strncpy(rspNameBuf, rspName, sizeof(rspNameBuf) - 1);
/* poll for shared memory */
auto start = std::chrono::steady_clock::now();
while (true) {
shmFd = shm_open(shmName, O_RDWR, 0);
if (shmFd >= 0) break;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed >= timeoutMs) return false;
usleep(10000);
}
mappedView = mmap(nullptr, RCX_RPC_SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, shmFd, 0);
if (mappedView == MAP_FAILED) { mappedView = nullptr; close(shmFd); shmFd = -1; return false; }
reqSem = sem_open(reqName, 0);
rspSem = sem_open(rspName, 0);
if (reqSem == SEM_FAILED || rspSem == SEM_FAILED) { disconnect(); return false; }
#endif
/* wait for payloadReady */
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
#ifdef _WIN32
while (!hdr->payloadReady) {
if (GetTickCount64() >= deadline) { disconnect(); return false; }
Sleep(5);
}
#else
while (!__atomic_load_n(&hdr->payloadReady, __ATOMIC_ACQUIRE)) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed >= timeoutMs) { disconnect(); return false; }
usleep(5000);
}
#endif
connected = true;
return true;
}
void disconnect()
{
#ifdef _WIN32
if (mappedView) { UnmapViewOfFile(mappedView); mappedView = nullptr; }
if (hShm) { CloseHandle(hShm); hShm = nullptr; }
if (hReqEvent) { CloseHandle(hReqEvent); hReqEvent = nullptr; }
if (hRspEvent) { CloseHandle(hRspEvent); hRspEvent = nullptr; }
#else
if (mappedView) { munmap(mappedView, RCX_RPC_SHM_SIZE); mappedView = nullptr; }
if (shmFd >= 0) { close(shmFd); shmFd = -1; }
if (reqSem != SEM_FAILED) { sem_close(reqSem); reqSem = SEM_FAILED; }
if (rspSem != SEM_FAILED) { sem_close(rspSem); rspSem = SEM_FAILED; }
#endif
connected = false;
}
/* ── low-level RPC round-trip ──────────────────────────────────── */
bool signalAndWait(int timeoutMs = 2000)
{
#ifdef _WIN32
SetEvent(hReqEvent);
return WaitForSingleObject(hRspEvent, (DWORD)timeoutMs) == WAIT_OBJECT_0;
#else
sem_post(reqSem);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeoutMs / 1000;
ts.tv_nsec += (timeoutMs % 1000) * 1000000L;
if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
return sem_timedwait(rspSem, &ts) == 0;
#endif
}
/* ── public API ────────────────────────────────────────────────── */
bool readSingle(uint64_t addr, void* buf, int len)
{
QMutexLocker lock(&mutex);
if (!connected || len <= 0) return false;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
auto* data = static_cast<uint8_t*>(mappedView) + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_READ_BATCH;
hdr->requestCount = 1;
hdr->status = RCX_RPC_STATUS_OK;
auto* entry = reinterpret_cast<RcxRpcReadEntry*>(data);
entry->address = addr;
entry->length = (uint32_t)len;
entry->dataOffset = sizeof(RcxRpcReadEntry);
if (!signalAndWait()) { connected = false; return false; }
memcpy(buf, data + entry->dataOffset, len);
return true;
}
bool writeSingle(uint64_t addr, const void* buf, int len)
{
QMutexLocker lock(&mutex);
if (!connected || len <= 0) return false;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
auto* data = static_cast<uint8_t*>(mappedView) + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_WRITE;
hdr->writeAddress = addr;
hdr->writeLength = (uint32_t)len;
hdr->status = RCX_RPC_STATUS_OK;
memcpy(data, buf, len);
if (!signalAndWait()) { connected = false; return false; }
return hdr->status == RCX_RPC_STATUS_OK;
}
QVector<RemoteProcessProvider::ModuleInfo> enumerateModules()
{
QVector<RemoteProcessProvider::ModuleInfo> result;
QMutexLocker lock(&mutex);
if (!connected) return result;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
auto* data = static_cast<uint8_t*>(mappedView) + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_ENUM_MODULES;
hdr->status = RCX_RPC_STATUS_OK;
if (!signalAndWait()) { connected = false; return result; }
if (hdr->status != RCX_RPC_STATUS_OK) return result;
uint32_t count = hdr->responseCount;
result.reserve((int)count);
for (uint32_t i = 0; i < count; ++i) {
auto* entry = reinterpret_cast<const RcxRpcModuleEntry*>(
data + i * sizeof(RcxRpcModuleEntry));
QString modName;
#ifdef _WIN32
modName = QString::fromWCharArray(
reinterpret_cast<const wchar_t*>(data + entry->nameOffset),
(int)(entry->nameLength / sizeof(wchar_t)));
#else
modName = QString::fromUtf8(
reinterpret_cast<const char*>(data + entry->nameOffset),
(int)entry->nameLength);
#endif
result.push_back(RemoteProcessProvider::ModuleInfo{modName, entry->base, entry->size});
}
return result;
}
bool ping()
{
QMutexLocker lock(&mutex);
if (!connected) return false;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
hdr->command = RPC_CMD_PING;
hdr->status = RCX_RPC_STATUS_OK;
if (!signalAndWait()) { connected = false; return false; }
return true;
}
void shutdown()
{
QMutexLocker lock(&mutex);
if (!connected) return;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
hdr->command = RPC_CMD_SHUTDOWN;
hdr->status = RCX_RPC_STATUS_OK;
signalAndWait(500);
connected = false;
}
};
/* ══════════════════════════════════════════════════════════════════════
* RemoteProcessProvider
* ══════════════════════════════════════════════════════════════════════ */
RemoteProcessProvider::RemoteProcessProvider(
uint32_t pid, const QString& processName,
std::shared_ptr<IpcClient> ipc)
: m_pid(pid)
, m_processName(processName)
, m_connected(ipc && ipc->connected)
, m_base(0)
, m_ipc(std::move(ipc))
{
if (m_connected) {
cacheModules();
// Read pointer size from payload's SHM header (0 means not set → default 8)
auto* hdr = m_ipc ? m_ipc->header() : nullptr;
if (hdr) {
uint32_t ps = hdr->pointerSize;
if (ps == 4 || ps == 8)
m_pointerSize = (int)ps;
}
}
}
RemoteProcessProvider::~RemoteProcessProvider() = default;
bool RemoteProcessProvider::read(uint64_t addr, void* buf, int len) const
{
if (!m_connected || len <= 0) return false;
bool ok = m_ipc->readSingle(addr, buf, len);
if (!ok) {
memset(buf, 0, (size_t)len);
/* update connectivity flag through mutable ipc */
const_cast<RemoteProcessProvider*>(this)->m_connected = m_ipc->connected;
}
return ok;
}
int RemoteProcessProvider::size() const
{
return m_connected ? 0x10000 : 0;
}
bool RemoteProcessProvider::write(uint64_t addr, const void* buf, int len)
{
if (!m_connected || len <= 0) return false;
bool ok = m_ipc->writeSingle(addr, buf, len);
if (!ok) m_connected = m_ipc->connected;
return ok;
}
QString RemoteProcessProvider::getSymbol(uint64_t addr) const
{
for (const auto& mod : m_modules) {
if (addr >= mod.base && addr < mod.base + mod.size) {
uint64_t off = addr - mod.base;
return QStringLiteral("%1+0x%2")
.arg(mod.name)
.arg(off, 0, 16, QChar('0'));
}
}
return {};
}
uint64_t RemoteProcessProvider::symbolToAddress(const QString& n) const
{
for (const auto& mod : m_modules) {
if (mod.name.compare(n, Qt::CaseInsensitive) == 0)
return mod.base;
}
return 0;
}
void RemoteProcessProvider::cacheModules()
{
m_modules = m_ipc->enumerateModules();
if (!m_modules.isEmpty())
m_base = m_modules.first().base;
}
/* ══════════════════════════════════════════════════════════════════════
* Injection helpers
* ══════════════════════════════════════════════════════════════════════ */
namespace {
/* Resolve payload DLL/SO path next to this plugin DLL/SO */
static QString payloadPath()
{
#ifdef _WIN32
HMODULE hSelf = nullptr;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&payloadPath), &hSelf);
WCHAR buf[MAX_PATH];
GetModuleFileNameW(hSelf, buf, MAX_PATH);
QFileInfo fi(QString::fromWCharArray(buf));
return fi.absolutePath() + QStringLiteral("/rcx_payload.dll");
#else
Dl_info info;
dladdr(reinterpret_cast<void*>(&payloadPath), &info);
QFileInfo fi(QString::fromUtf8(info.dli_fname));
return fi.absolutePath() + QStringLiteral("/rcx_payload.so");
#endif
}
#ifdef _WIN32
/* ── Windows injection: CreateRemoteThread + LoadLibraryA ─────────── */
static bool injectPayload(uint32_t pid, QString* errorMsg)
{
QString path = payloadPath();
QByteArray pathUtf8 = QDir::toNativeSeparators(path).toLocal8Bit();
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProc) {
if (errorMsg)
*errorMsg = QStringLiteral("OpenProcess failed (error %1).\n"
"Try running as Administrator.")
.arg(GetLastError());
return false;
}
/* allocate + write path string in target */
SIZE_T pathLen = (SIZE_T)(pathUtf8.size() + 1);
void* remotePath = VirtualAllocEx(hProc, nullptr, pathLen,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remotePath) {
if (errorMsg) *errorMsg = QStringLiteral("VirtualAllocEx failed.");
CloseHandle(hProc);
return false;
}
WriteProcessMemory(hProc, remotePath, pathUtf8.constData(), pathLen, nullptr);
/* Step 1: LoadLibraryA — loads the DLL (DllMain is minimal) */
HMODULE hK32 = GetModuleHandleA("kernel32.dll");
auto pLoadLib = reinterpret_cast<LPTHREAD_START_ROUTINE>(
GetProcAddress(hK32, "LoadLibraryA"));
HANDLE hThread = CreateRemoteThread(hProc, nullptr, 0,
pLoadLib, remotePath, 0, nullptr);
if (!hThread) {
if (errorMsg) *errorMsg = QStringLiteral("CreateRemoteThread failed (error %1).")
.arg(GetLastError());
VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE);
CloseHandle(hProc);
return false;
}
WaitForSingleObject(hThread, 10000);
DWORD exitCode = 0;
GetExitCodeThread(hThread, &exitCode);
CloseHandle(hThread);
VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE);
if (exitCode == 0) {
CloseHandle(hProc);
if (errorMsg) *errorMsg = QStringLiteral("LoadLibrary returned NULL in target.\n"
"Ensure rcx_payload.dll is in: %1").arg(path);
return false;
}
/* Step 2: Call RcxPayloadInit() — safe to create timer queues now
(loader lock is no longer held after LoadLibrary returned) */
HMODULE hPayloadRemote = (HMODULE)(uintptr_t)exitCode;
auto pGetProcAddr = reinterpret_cast<FARPROC(WINAPI*)(HMODULE, LPCSTR)>(
GetProcAddress(hK32, "GetProcAddress"));
/* Write "RcxPayloadInit\0" into target, call GetProcAddress remotely */
const char initName[] = "RcxPayloadInit";
void* remoteInitName = VirtualAllocEx(hProc, nullptr, sizeof(initName),
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (remoteInitName) {
WriteProcessMemory(hProc, remoteInitName, initName, sizeof(initName), nullptr);
/* We need to call GetProcAddress(hPayload, "RcxPayloadInit") then call the result.
Simpler approach: write small shellcode that does both calls. */
uint8_t shellcode[128];
int off = 0;
/* sub rsp, 40 ; shadow space + alignment */
shellcode[off++] = 0x48; shellcode[off++] = 0x83; shellcode[off++] = 0xEC; shellcode[off++] = 0x28;
/* mov rcx, hPayloadRemote ; first arg = module handle */
shellcode[off++] = 0x48; shellcode[off++] = 0xB9;
uint64_t hMod = (uint64_t)(uintptr_t)hPayloadRemote;
memcpy(shellcode + off, &hMod, 8); off += 8;
/* mov rdx, remoteInitName ; second arg = "RcxPayloadInit" */
shellcode[off++] = 0x48; shellcode[off++] = 0xBA;
uint64_t pName = (uint64_t)(uintptr_t)remoteInitName;
memcpy(shellcode + off, &pName, 8); off += 8;
/* mov rax, GetProcAddress */
shellcode[off++] = 0x48; shellcode[off++] = 0xB8;
uint64_t pGPA = (uint64_t)(uintptr_t)pGetProcAddr;
memcpy(shellcode + off, &pGPA, 8); off += 8;
/* call rax ; rax = RcxPayloadInit */
shellcode[off++] = 0xFF; shellcode[off++] = 0xD0;
/* test rax, rax */
shellcode[off++] = 0x48; shellcode[off++] = 0x85; shellcode[off++] = 0xC0;
/* jz skip (jump over the call if null) */
shellcode[off++] = 0x74; shellcode[off++] = 0x02;
/* call rax ; RcxPayloadInit() */
shellcode[off++] = 0xFF; shellcode[off++] = 0xD0;
/* skip: add rsp, 40 */
shellcode[off++] = 0x48; shellcode[off++] = 0x83; shellcode[off++] = 0xC4; shellcode[off++] = 0x28;
/* ret */
shellcode[off++] = 0xC3;
void* remoteCode = VirtualAllocEx(hProc, nullptr, (SIZE_T)off,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (remoteCode) {
WriteProcessMemory(hProc, remoteCode, shellcode, (SIZE_T)off, nullptr);
HANDLE hThread2 = CreateRemoteThread(hProc, nullptr, 0,
(LPTHREAD_START_ROUTINE)remoteCode, nullptr, 0, nullptr);
if (hThread2) {
WaitForSingleObject(hThread2, 10000);
CloseHandle(hThread2);
}
VirtualFreeEx(hProc, remoteCode, 0, MEM_RELEASE);
}
VirtualFreeEx(hProc, remoteInitName, 0, MEM_RELEASE);
}
CloseHandle(hProc);
return true;
}
#else
/* ── Linux injection: ptrace + dlopen ─────────────────────────────── */
static uint64_t findLibBase(pid_t pid, const char* libName)
{
char mapsPath[64];
snprintf(mapsPath, sizeof(mapsPath), "/proc/%d/maps", pid);
FILE* f = fopen(mapsPath, "r");
if (!f) return 0;
char line[1024];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, libName)) {
uint64_t base;
if (sscanf(line, "%lx-", &base) == 1) {
fclose(f);
return base;
}
}
}
fclose(f);
return 0;
}
static uint64_t findSyscallInsn(pid_t pid)
{
char mapsPath[64];
snprintf(mapsPath, sizeof(mapsPath), "/proc/%d/maps", pid);
FILE* f = fopen(mapsPath, "r");
if (!f) return 0;
char line[1024];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "libc") && strstr(line, "r-xp")) {
uint64_t start, end;
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
fclose(f);
/* scan for 0F 05 (syscall) */
char memPath[64];
snprintf(memPath, sizeof(memPath), "/proc/%d/mem", pid);
int memFd = open(memPath, O_RDONLY);
if (memFd < 0) return 0;
uint8_t buf[4096];
for (uint64_t off = start; off < end; off += sizeof(buf)) {
ssize_t n = pread(memFd, buf, sizeof(buf), (off_t)off);
if (n <= 1) break;
for (ssize_t i = 0; i + 1 < n; ++i) {
if (buf[i] == 0x0F && buf[i + 1] == 0x05) {
close(memFd);
return off + (uint64_t)i;
}
}
}
close(memFd);
return 0;
}
}
fclose(f);
return 0;
}
static bool writeTargetMem(pid_t pid, uint64_t addr, const void* src, size_t len)
{
const uint8_t* p = static_cast<const uint8_t*>(src);
for (size_t i = 0; i < len; i += sizeof(long)) {
long val = 0;
size_t chunk = (len - i < sizeof(long)) ? (len - i) : sizeof(long);
if (chunk < sizeof(long)) {
errno = 0;
val = ptrace(PTRACE_PEEKDATA, pid, (void*)(addr + i), nullptr);
if (errno) return false;
}
memcpy(&val, p + i, chunk);
if (ptrace(PTRACE_POKEDATA, pid, (void*)(addr + i), (void*)val) < 0)
return false;
}
return true;
}
static bool injectPayload(uint32_t pid, QString* errorMsg)
{
QString path = payloadPath();
QByteArray pathUtf8 = path.toUtf8();
if (ptrace(PTRACE_ATTACH, (pid_t)pid, nullptr, nullptr) < 0) {
if (errorMsg)
*errorMsg = QStringLiteral("ptrace attach failed: %1\n"
"Check /proc/sys/kernel/yama/ptrace_scope or run as root.")
.arg(strerror(errno));
return false;
}
int status;
waitpid((pid_t)pid, &status, 0);
/* save registers */
struct user_regs_struct savedRegs, regs;
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &savedRegs);
regs = savedRegs;
/* find syscall instruction in target's libc */
uint64_t syscallAddr = findSyscallInsn((pid_t)pid);
if (!syscallAddr) {
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (errorMsg) *errorMsg = QStringLiteral("Could not find syscall instruction in target.");
return false;
}
/* find dlopen in target via libc offset technique */
void* ourDlopen = dlsym(RTLD_DEFAULT, "dlopen");
uint64_t ourLibcBase = findLibBase(getpid(), "libc");
uint64_t targetLibcBase = findLibBase((pid_t)pid, "libc");
if (!ourDlopen || !ourLibcBase || !targetLibcBase) {
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (errorMsg) *errorMsg = QStringLiteral("Could not resolve dlopen address.");
return false;
}
uint64_t targetDlopen = targetLibcBase + ((uint64_t)ourDlopen - ourLibcBase);
/* call mmap in target via syscall: mmap(0, 4096, RWX, MAP_PRIVATE|MAP_ANON, -1, 0) */
regs.rax = 9; /* __NR_mmap */
regs.rdi = 0;
regs.rsi = 4096;
regs.rdx = 7; /* PROT_READ|PROT_WRITE|PROT_EXEC */
regs.r10 = 0x22; /* MAP_PRIVATE|MAP_ANONYMOUS */
regs.r8 = (uint64_t)-1;
regs.r9 = 0;
regs.rip = syscallAddr;
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &regs);
ptrace(PTRACE_SINGLESTEP, (pid_t)pid, nullptr, nullptr);
waitpid((pid_t)pid, &status, 0);
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &regs);
uint64_t mmapPage = regs.rax;
if ((int64_t)mmapPage < 0 || mmapPage == 0) {
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &savedRegs);
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (errorMsg) *errorMsg = QStringLiteral("mmap in target failed.");
return false;
}
/* write path string at start of page */
writeTargetMem((pid_t)pid, mmapPage, pathUtf8.constData(), (size_t)(pathUtf8.size() + 1));
/* write shellcode after path:
* mov rdi, pathAddr (48 BF xxxxxxxx)
* mov rsi, 2 (48 BE 02000000 00000000)
* mov rax, dlopenAddr (48 B8 xxxxxxxx)
* call rax (FF D0)
* int3 (CC)
*/
uint64_t pathAddr = mmapPage;
uint64_t codeAddr = mmapPage + ((pathUtf8.size() + 1 + 15) & ~15ULL);
uint8_t sc[64];
int len = 0;
/* mov rdi, imm64 */
sc[len++] = 0x48; sc[len++] = 0xBF;
memcpy(sc + len, &pathAddr, 8); len += 8;
/* mov rsi, 2 (RTLD_NOW) */
sc[len++] = 0x48; sc[len++] = 0xBE;
uint64_t rtldNow = 2;
memcpy(sc + len, &rtldNow, 8); len += 8;
/* mov rax, dlopen */
sc[len++] = 0x48; sc[len++] = 0xB8;
memcpy(sc + len, &targetDlopen, 8); len += 8;
/* call rax */
sc[len++] = 0xFF; sc[len++] = 0xD0;
/* int3 */
sc[len++] = 0xCC;
writeTargetMem((pid_t)pid, codeAddr, sc, (size_t)len);
/* execute shellcode */
regs = savedRegs;
regs.rip = codeAddr;
regs.rsp = (mmapPage + 4096) & ~0xFULL;
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &regs);
ptrace(PTRACE_CONT, (pid_t)pid, nullptr, nullptr);
waitpid((pid_t)pid, &status, 0);
bool ok = false;
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &regs);
ok = (regs.rax != 0);
}
/* clean up: munmap the page via syscall */
struct user_regs_struct cleanRegs = savedRegs;
cleanRegs.rax = 11; /* __NR_munmap */
cleanRegs.rdi = mmapPage;
cleanRegs.rsi = 4096;
cleanRegs.rip = syscallAddr;
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &cleanRegs);
ptrace(PTRACE_SINGLESTEP, (pid_t)pid, nullptr, nullptr);
waitpid((pid_t)pid, &status, 0);
/* restore and detach */
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &savedRegs);
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (!ok && errorMsg)
*errorMsg = QStringLiteral("dlopen failed in target.\n"
"Ensure payload is at: %1").arg(path);
return ok;
}
#endif /* _WIN32 / linux injection */
} /* anonymous namespace */
/* ══════════════════════════════════════════════════════════════════════
* RemoteProcessMemoryPlugin
* ══════════════════════════════════════════════════════════════════════ */
RemoteProcessMemoryPlugin::RemoteProcessMemoryPlugin() = default;
RemoteProcessMemoryPlugin::~RemoteProcessMemoryPlugin() = default;
QIcon RemoteProcessMemoryPlugin::Icon() const
{
return qApp->style()->standardIcon(QStyle::SP_DriveNetIcon);
}
bool RemoteProcessMemoryPlugin::canHandle(const QString& target) const
{
return target.startsWith(QStringLiteral("rpm:"));
}
std::unique_ptr<rcx::Provider>
RemoteProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
{
/* target = "rpm:{pid}:{name}" */
QStringList parts = target.split(':');
if (parts.size() < 3 || parts[0] != QStringLiteral("rpm")) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid target: ") + target;
return nullptr;
}
bool ok;
uint32_t pid = parts[1].toUInt(&ok);
QString name = parts.mid(2).join(':'); /* name may contain colons */
if (!ok || pid == 0) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target.");
return nullptr;
}
auto ipc = getOrCreateConnection(pid, errorMsg);
if (!ipc) return nullptr;
return std::make_unique<RemoteProcessProvider>(pid, name, ipc);
}
uint64_t RemoteProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
{
/* Read imageBase directly from the shared-memory header -- zero IPC cost.
The payload filled it at init from PEB->Ldr (Win) / /proc/self/maps (Linux). */
QStringList parts = target.split(':');
if (parts.size() < 2 || parts[0] != QStringLiteral("rpm"))
return 0;
bool ok;
uint32_t pid = parts[1].toUInt(&ok);
if (!ok) return 0;
QMutexLocker lock(&m_connectionsMutex);
auto it = m_connections.constFind(pid);
if (it == m_connections.constEnd() || !(*it)->connected)
return 0;
auto* hdr = static_cast<const RcxRpcHeader*>((*it)->mappedView);
return hdr->imageBase;
}
bool RemoteProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
{
/* ── 1. pick a process ── */
QVector<PluginProcessInfo> pluginProcs = enumerateProcesses();
QList<ProcessInfo> procs;
for (const auto& pi : pluginProcs) {
ProcessInfo info;
info.pid = pi.pid;
info.name = pi.name;
info.path = pi.path;
info.icon = pi.icon;
procs.append(info);
}
ProcessPicker picker(procs, parent);
if (picker.exec() != QDialog::Accepted) return false;
uint32_t pid = picker.selectedProcessId();
QString name = picker.selectedProcessName();
/* ── 2. ask inject or connect ── */
QMessageBox box(parent);
box.setWindowTitle(QStringLiteral("Remote Process Memory"));
box.setText(QStringLiteral("Connect to %1 (PID %2)").arg(name).arg(pid));
box.setInformativeText(QStringLiteral("Choose how to connect to the target:"));
QAbstractButton* injectBtn = box.addButton(QStringLiteral("Inject Payload"), QMessageBox::ActionRole);
QAbstractButton* connectBtn = box.addButton(QStringLiteral("Already Injected"), QMessageBox::ActionRole);
box.addButton(QMessageBox::Cancel);
box.exec();
QAbstractButton* clicked = box.clickedButton();
if (clicked == injectBtn) {
QString injectErr;
if (!injectPayload(pid, &injectErr)) {
QMessageBox::critical(parent, QStringLiteral("Injection Failed"), injectErr);
return false;
}
*target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true;
}
else if (clicked == connectBtn) {
*target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true;
}
return false;
}
QVector<PluginProcessInfo> RemoteProcessMemoryPlugin::enumerateProcesses()
{
QVector<PluginProcessInfo> procs;
#ifdef _WIN32
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return procs;
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snap, &entry)) {
do {
PluginProcessInfo info;
info.pid = entry.th32ProcessID;
info.name = QString::fromWCharArray(entry.szExeFile);
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, entry.th32ProcessID);
if (hProc) {
wchar_t path[MAX_PATH * 2];
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
if (QueryFullProcessImageNameW(hProc, 0, path, &pathLen)) {
info.path = QString::fromWCharArray(path);
SHFILEINFOW sfi = {};
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi),
SHGFI_ICON | SHGFI_SMALLICON) && sfi.hIcon) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
info.icon = QIcon(QPixmap::fromImage(QImage::fromHICON(sfi.hIcon)));
#else
info.icon = QIcon(QtWin::fromHICON(sfi.hIcon));
#endif
DestroyIcon(sfi.hIcon);
}
}
CloseHandle(hProc);
}
procs.append(info);
} while (Process32NextW(snap, &entry));
}
CloseHandle(snap);
#else
QDir procDir(QStringLiteral("/proc"));
QIcon defIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
for (const QString& entry : procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
bool ok;
uint32_t pid = entry.toUInt(&ok);
if (!ok || pid == 0) continue;
QFile commFile(QStringLiteral("/proc/%1/comm").arg(pid));
if (!commFile.open(QIODevice::ReadOnly)) continue;
QString procName = QString::fromUtf8(commFile.readAll()).trimmed();
commFile.close();
if (procName.isEmpty()) continue;
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
if (::access(memPath.toUtf8().constData(), R_OK) != 0) continue;
QFileInfo exeInfo(QStringLiteral("/proc/%1/exe").arg(pid));
PluginProcessInfo info;
info.pid = pid;
info.name = procName;
info.path = exeInfo.exists() ? exeInfo.symLinkTarget() : QString();
info.icon = defIcon;
procs.append(info);
}
#endif
return procs;
}
std::shared_ptr<IpcClient>
RemoteProcessMemoryPlugin::getOrCreateConnection(
uint32_t pid, QString* errorMsg)
{
QMutexLocker lock(&m_connectionsMutex);
auto it = m_connections.find(pid);
if (it != m_connections.end() && (*it)->connected)
return *it;
auto ipc = std::make_shared<IpcClient>();
if (!ipc->connect(pid)) {
if (errorMsg)
*errorMsg = QStringLiteral("Failed to connect IPC to PID %1.\n"
"Is the payload running?").arg(pid);
return nullptr;
}
m_connections[pid] = ipc;
return ipc;
}
/* ── Plugin factory ───────────────────────────────────────────────── */
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
{
return new RemoteProcessMemoryPlugin();
}

View File

@@ -0,0 +1,88 @@
#pragma once
#include "../../src/iplugin.h"
#include "../../src/providers/provider.h"
#include <cstdint>
#include <memory>
#include <QMutex>
#include <QHash>
#include <QVector>
struct IpcClient; /* defined in .cpp */
/* ── Provider ─────────────────────────────────────────────────────── */
class RemoteProcessProvider : public rcx::Provider
{
public:
struct ModuleInfo { QString name; uint64_t base; uint64_t size; };
RemoteProcessProvider(uint32_t pid, const QString& processName,
std::shared_ptr<IpcClient> ipc);
~RemoteProcessProvider() override;
/* required */
bool read(uint64_t addr, void* buf, int len) const override;
int size() const override;
/* optional */
bool write(uint64_t addr, const void* buf, int len) override;
bool isWritable() const override { return m_connected; }
QString name() const override { return m_processName; }
QString kind() const override { return QStringLiteral("RemoteProcess"); }
bool isLive() const override { return true; }
uint64_t base() const override { return m_base; }
int pointerSize() const override { return m_pointerSize; }
bool isReadable(uint64_t, int len) const override { return m_connected && len >= 0; }
QString getSymbol(uint64_t addr) const override;
uint64_t symbolToAddress(const QString& n) const override;
uint32_t pid() const { return m_pid; }
private:
void cacheModules();
uint32_t m_pid;
QString m_processName;
bool m_connected;
uint64_t m_base;
int m_pointerSize = 8;
mutable std::shared_ptr<IpcClient> m_ipc;
QVector<ModuleInfo> m_modules;
};
/* ── Plugin ───────────────────────────────────────────────────────── */
class RemoteProcessMemoryPlugin : public IProviderPlugin
{
public:
RemoteProcessMemoryPlugin();
~RemoteProcessMemoryPlugin() override;
std::string Name() const override { return "Remote Process Memory"; }
std::string Version() const override { return "1.0.0"; }
std::string Author() const override { return "Reclass"; }
std::string Description() const override {
return "Read/write memory via injected payload (shared-memory IPC)";
}
k_ELoadType LoadType() const override { return k_ELoadTypeManual; }
QIcon Icon() const override;
bool canHandle(const QString& target) const override;
std::unique_ptr<rcx::Provider> createProvider(const QString& target,
QString* errorMsg) override;
uint64_t getInitialBaseAddress(const QString& target) const override;
bool selectTarget(QWidget* parent, QString* target) override;
bool providesProcessList() const override { return true; }
QVector<PluginProcessInfo> enumerateProcesses() override;
private:
std::shared_ptr<IpcClient> getOrCreateConnection(
uint32_t pid, QString* errorMsg);
mutable QMutex m_connectionsMutex;
QHash<uint32_t, std::shared_ptr<IpcClient>> m_connections;
};
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();

View File

@@ -0,0 +1,612 @@
/*
* rcx_payload -- injected into target process.
*
* Pure Win32 / POSIX, NO Qt, minimal footprint.
* Creates the main IPC channel (shared memory + events/semaphores)
* using PID-only naming and uses a timer queue for polling.
*/
#include "../rcx_rpc_protocol.h"
#ifdef _WIN32
/* ===================================================================
* WINDOWS implementation
* =================================================================== */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>
/* ── globals ──────────────────────────────────────────────────────── */
static HANDLE g_hShm = nullptr;
static void* g_mappedView = nullptr;
static HANDLE g_hReqEvent = nullptr;
static HANDLE g_hRspEvent = nullptr;
static HANDLE g_hTimerQueue = nullptr;
static HANDLE g_hPollTimer = nullptr;
static volatile LONG g_initialized = 0;
/* ── memory safety via VirtualQuery ────────────────────────────────── */
inline bool IsReadableProtect(DWORD p)
{
if (p & (PAGE_NOACCESS | PAGE_GUARD))
return false;
const DWORD readable =
PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY |
PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
return (p & readable) != 0;
}
inline bool IsWritableProtect(DWORD p)
{
if (p & (PAGE_NOACCESS | PAGE_GUARD))
return false;
const DWORD writable =
PAGE_READWRITE | PAGE_WRITECOPY |
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
return (p & writable) != 0;
}
/* Check that the full range [addr, addr+len) is covered by readable pages. */
static bool IsRangeReadable(uintptr_t addr, uint32_t len)
{
uintptr_t end = addr + len;
uintptr_t cur = addr;
while (cur < end) {
MEMORY_BASIC_INFORMATION mbi{};
if (VirtualQuery(reinterpret_cast<LPCVOID>(cur), &mbi, sizeof(mbi)) == 0)
return false;
if (mbi.State != MEM_COMMIT || !IsReadableProtect(mbi.Protect))
return false;
uintptr_t regionEnd = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
cur = regionEnd;
}
return true;
}
static bool IsRangeWritable(uintptr_t addr, uint32_t len)
{
uintptr_t end = addr + len;
uintptr_t cur = addr;
while (cur < end) {
MEMORY_BASIC_INFORMATION mbi{};
if (VirtualQuery(reinterpret_cast<LPCVOID>(cur), &mbi, sizeof(mbi)) == 0)
return false;
if (mbi.State != MEM_COMMIT || !IsWritableProtect(mbi.Protect))
return false;
uintptr_t regionEnd = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
cur = regionEnd;
}
return true;
}
/* ── command handlers ─────────────────────────────────────────────── */
static void handle_read_batch(RcxRpcHeader* hdr, uint8_t* data)
{
auto* entries = reinterpret_cast<RcxRpcReadEntry*>(data);
for (uint32_t i = 0; i < hdr->requestCount; ++i) {
uint8_t* dest = data + entries[i].dataOffset;
uintptr_t src = static_cast<uintptr_t>(entries[i].address);
if (IsRangeReadable(src, entries[i].length)) {
memcpy(dest, reinterpret_cast<const void*>(src), entries[i].length);
} else {
memset(dest, 0, entries[i].length);
hdr->status = RCX_RPC_STATUS_PARTIAL;
}
/* SEH fallback (commented out, kept for reference):
__try {
memcpy(dest, reinterpret_cast<const void*>(src), entries[i].length);
} __except (EXCEPTION_EXECUTE_HANDLER) {
memset(dest, 0, entries[i].length);
hdr->status = RCX_RPC_STATUS_PARTIAL;
}
*/
}
hdr->responseCount = hdr->requestCount;
}
static void handle_write(RcxRpcHeader* hdr, uint8_t* data)
{
uintptr_t dst = static_cast<uintptr_t>(hdr->writeAddress);
if (IsRangeWritable(dst, hdr->writeLength)) {
memcpy(reinterpret_cast<void*>(dst), data, hdr->writeLength);
} else {
hdr->status = RCX_RPC_STATUS_ERROR;
}
/* SEH fallback (commented out, kept for reference):
__try {
memcpy(reinterpret_cast<void*>(dst), data, hdr->writeLength);
} __except (EXCEPTION_EXECUTE_HANDLER) {
hdr->status = RCX_RPC_STATUS_ERROR;
}
*/
}
static void handle_enum_modules(RcxRpcHeader* hdr, uint8_t* data)
{
HANDLE hProc = GetCurrentProcess();
HMODULE mods[1024];
DWORD needed = 0;
if (!EnumProcessModules(hProc, mods, sizeof(mods), &needed)) {
hdr->status = RCX_RPC_STATUS_ERROR;
hdr->responseCount = 0;
return;
}
int count = (int)(needed / sizeof(HMODULE));
if (count > 1024) count = 1024;
uint32_t entryBytes = (uint32_t)(count * sizeof(RcxRpcModuleEntry));
uint32_t nameDataOff = entryBytes;
for (int i = 0; i < count; ++i) {
MODULEINFO mi{};
WCHAR modName[MAX_PATH];
GetModuleInformation(hProc, mods[i], &mi, sizeof(mi));
int nameLen = (int)GetModuleBaseNameW(hProc, mods[i], modName, MAX_PATH);
uint32_t nameBytes = (uint32_t)(nameLen * sizeof(WCHAR));
auto* entry = reinterpret_cast<RcxRpcModuleEntry*>(data + i * sizeof(RcxRpcModuleEntry));
entry->base = reinterpret_cast<uint64_t>(mi.lpBaseOfDll);
entry->size = static_cast<uint64_t>(mi.SizeOfImage);
entry->nameOffset = nameDataOff;
entry->nameLength = nameBytes;
if (nameDataOff + nameBytes <= RCX_RPC_DATA_SIZE) {
memcpy(data + nameDataOff, modName, nameBytes);
nameDataOff += nameBytes;
}
}
hdr->responseCount = (uint32_t)count;
hdr->totalDataUsed = nameDataOff;
hdr->status = RCX_RPC_STATUS_OK;
}
/* forward declaration */
void RcxPayloadCleanup();
/* ── timer callback (non-blocking poll) ───────────────────────────── */
static VOID CALLBACK RcxPollTimerCallback(PVOID, BOOLEAN)
{
if (!g_mappedView || !g_hReqEvent || !g_hRspEvent)
return;
/* non-blocking check: is there a pending request? */
DWORD rc = WaitForSingleObject(g_hReqEvent, 0);
if (rc != WAIT_OBJECT_0)
return;
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
auto* data = reinterpret_cast<uint8_t*>(g_mappedView) + RCX_RPC_DATA_OFFSET;
hdr->status = RCX_RPC_STATUS_OK;
switch (static_cast<RcxRpcCommand>(hdr->command)) {
case RPC_CMD_READ_BATCH: handle_read_batch(hdr, data); break;
case RPC_CMD_WRITE: handle_write(hdr, data); break;
case RPC_CMD_ENUM_MODULES: handle_enum_modules(hdr, data); break;
case RPC_CMD_PING: break;
case RPC_CMD_SHUTDOWN:
RcxPayloadCleanup();
return;
default:
hdr->status = RCX_RPC_STATUS_ERROR;
break;
}
SetEvent(g_hRspEvent);
}
/* ── cleanup ──────────────────────────────────────────────────────── */
void RcxPayloadCleanup()
{
if (!InterlockedCompareExchange(&g_initialized, 0, 0))
return;
/* stop the poll timer first */
if (g_hTimerQueue) {
DeleteTimerQueueEx(g_hTimerQueue, INVALID_HANDLE_VALUE); /* waits for callbacks */
g_hTimerQueue = nullptr;
g_hPollTimer = nullptr;
}
/* mark not-ready */
if (g_mappedView) {
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 0);
}
if (g_mappedView) { UnmapViewOfFile(g_mappedView); g_mappedView = nullptr; }
if (g_hShm) { CloseHandle(g_hShm); g_hShm = nullptr; }
if (g_hReqEvent) { CloseHandle(g_hReqEvent); g_hReqEvent = nullptr; }
if (g_hRspEvent) { CloseHandle(g_hRspEvent); g_hRspEvent = nullptr; }
InterlockedExchange(&g_initialized, 0);
}
/* ── init (called AFTER DllMain returns — safe for timer queues) ── */
extern "C" __declspec(dllexport)
bool RcxPayloadInit()
{
if (InterlockedCompareExchange(&g_initialized, 1, 0) != 0)
return true; /* already initialized */
uint32_t pid = GetCurrentProcessId();
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
g_hShm = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_SHM_SIZE, shmName);
if (!g_hShm) {
InterlockedExchange(&g_initialized, 0);
return false;
}
g_mappedView = MapViewOfFile(g_hShm, FILE_MAP_ALL_ACCESS, 0, 0, RCX_RPC_SHM_SIZE);
if (!g_mappedView) {
CloseHandle(g_hShm); g_hShm = nullptr;
InterlockedExchange(&g_initialized, 0);
return false;
}
memset(g_mappedView, 0, RCX_RPC_HEADER_SIZE);
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
hdr->version = RCX_RPC_VERSION;
/* image base from PEB */
{
uint64_t peb;
asm volatile("mov %%gs:0x60, %0" : "=r"(peb));
uint64_t ldr = *reinterpret_cast<uint64_t*>(peb + 0x18);
uint64_t firstLink = *reinterpret_cast<uint64_t*>(ldr + 0x10);
hdr->imageBase = *reinterpret_cast<uint64_t*>(firstLink + 0x30);
}
g_hReqEvent = CreateEventA(nullptr, FALSE, FALSE, reqName);
g_hRspEvent = CreateEventA(nullptr, FALSE, FALSE, rspName);
if (!g_hReqEvent || !g_hRspEvent) {
RcxPayloadCleanup();
return false;
}
/* create dedicated timer queue + fast poll timer (10ms interval) */
g_hTimerQueue = CreateTimerQueue();
if (!g_hTimerQueue) {
RcxPayloadCleanup();
return false;
}
if (!CreateTimerQueueTimer(&g_hPollTimer, g_hTimerQueue,
RcxPollTimerCallback, nullptr,
0, /* start immediately */
10, /* 10ms repeat */
WT_EXECUTEDEFAULT)) {
RcxPayloadCleanup();
return false;
}
/* mark ready */
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 1);
return true;
}
/* ── DllMain — minimal, no heavy work under loader lock ───────────── */
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_DETACH) {
RcxPayloadCleanup();
}
return TRUE;
}
#else
/* ===================================================================
* LINUX implementation
* =================================================================== */
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
/* ── globals ──────────────────────────────────────────────────────── */
static int g_shmFd = -1;
static void* g_mappedView = nullptr;
static sem_t* g_reqSem = SEM_FAILED;
static sem_t* g_rspSem = SEM_FAILED;
static pthread_t g_thread;
static volatile int g_shutdown = 0;
static volatile int g_threadRunning = 0;
static int g_memFd = -1; /* /proc/self/mem for safe access */
static char g_shmName[128];
static char g_reqName[128];
static char g_rspName[128];
/* ── safe memory access via /proc/self/mem ────────────────────────── */
static void safe_read(uint64_t addr, void* dest, uint32_t len, uint32_t* status)
{
ssize_t n = pread(g_memFd, dest, len, (off_t)addr);
if (n < (ssize_t)len) {
if (n > 0)
memset((uint8_t*)dest + n, 0, len - (uint32_t)n);
else
memset(dest, 0, len);
*status = RCX_RPC_STATUS_PARTIAL;
}
}
static void safe_write(uint64_t addr, const void* src, uint32_t len, uint32_t* status)
{
ssize_t n = pwrite(g_memFd, src, len, (off_t)addr);
if (n < (ssize_t)len)
*status = RCX_RPC_STATUS_ERROR;
}
/* ── command handlers ─────────────────────────────────────────────── */
static void handle_read_batch(RcxRpcHeader* hdr, uint8_t* data)
{
auto* entries = reinterpret_cast<RcxRpcReadEntry*>(data);
for (uint32_t i = 0; i < hdr->requestCount; ++i) {
uint8_t* dest = data + entries[i].dataOffset;
safe_read(entries[i].address, dest, entries[i].length, &hdr->status);
}
hdr->responseCount = hdr->requestCount;
}
static void handle_write(RcxRpcHeader* hdr, uint8_t* data)
{
safe_write(hdr->writeAddress, data, hdr->writeLength, &hdr->status);
}
static void handle_enum_modules(RcxRpcHeader* hdr, uint8_t* data)
{
FILE* f = fopen("/proc/self/maps", "r");
if (!f) {
hdr->status = RCX_RPC_STATUS_ERROR;
hdr->responseCount = 0;
return;
}
/* first pass: collect unique file-backed mappings */
struct ModRange { uint64_t base; uint64_t end; char path[512]; };
static ModRange modules[512]; /* static to avoid large stack alloc */
int modCount = 0;
char line[1024];
while (fgets(line, sizeof(line), f) && modCount < 512) {
uint64_t start, end;
char perms[8] = {}, path[512] = {};
if (sscanf(line, "%lx-%lx %7s %*x %*x:%*x %*u %511[^\n]",
&start, &end, perms, path) < 4)
continue;
/* skip non-file / special mappings */
/* trim leading whitespace from path */
char* p = path;
while (*p == ' ' || *p == '\t') ++p;
if (*p != '/') continue;
if (strncmp(p, "/dev/", 5) == 0) continue;
if (strncmp(p, "/memfd:", 7) == 0) continue;
bool found = false;
for (int i = 0; i < modCount; ++i) {
if (strcmp(modules[i].path, p) == 0) {
if (start < modules[i].base) modules[i].base = start;
if (end > modules[i].end) modules[i].end = end;
found = true;
break;
}
}
if (!found) {
modules[modCount].base = start;
modules[modCount].end = end;
strncpy(modules[modCount].path, p, 511);
modules[modCount].path[511] = '\0';
++modCount;
}
}
fclose(f);
/* write entries + name strings into data region */
uint32_t entryBytes = (uint32_t)(modCount * sizeof(RcxRpcModuleEntry));
uint32_t nameDataOff = entryBytes;
for (int i = 0; i < modCount; ++i) {
const char* basename = strrchr(modules[i].path, '/');
basename = basename ? basename + 1 : modules[i].path;
uint32_t nameLen = (uint32_t)strlen(basename);
auto* entry = reinterpret_cast<RcxRpcModuleEntry*>(
data + (uint32_t)i * sizeof(RcxRpcModuleEntry));
entry->base = modules[i].base;
entry->size = modules[i].end - modules[i].base;
entry->nameOffset = nameDataOff;
entry->nameLength = nameLen;
if (nameDataOff + nameLen <= RCX_RPC_DATA_SIZE) {
memcpy(data + nameDataOff, basename, nameLen);
nameDataOff += nameLen;
}
}
hdr->responseCount = (uint32_t)modCount;
hdr->totalDataUsed = nameDataOff;
hdr->status = RCX_RPC_STATUS_OK;
}
/* ── server thread ────────────────────────────────────────────────── */
static void* server_thread_func(void*)
{
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
auto* data = reinterpret_cast<uint8_t*>(g_mappedView) + RCX_RPC_DATA_OFFSET;
__atomic_store_n(&hdr->payloadReady, 1, __ATOMIC_RELEASE);
while (!__atomic_load_n(&g_shutdown, __ATOMIC_ACQUIRE)) {
/* timed wait: 250ms */
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += 250000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000000000;
}
int rc = sem_timedwait(g_reqSem, &ts);
if (rc != 0) {
if (errno == ETIMEDOUT) continue;
break;
}
hdr->status = RCX_RPC_STATUS_OK;
switch (static_cast<RcxRpcCommand>(hdr->command)) {
case RPC_CMD_READ_BATCH: handle_read_batch(hdr, data); break;
case RPC_CMD_WRITE: handle_write(hdr, data); break;
case RPC_CMD_ENUM_MODULES: handle_enum_modules(hdr, data); break;
case RPC_CMD_PING: break;
case RPC_CMD_SHUTDOWN:
__atomic_store_n(&g_shutdown, 1, __ATOMIC_RELEASE);
break;
default:
hdr->status = RCX_RPC_STATUS_ERROR;
break;
}
sem_post(g_rspSem);
if (static_cast<RcxRpcCommand>(hdr->command) == RPC_CMD_SHUTDOWN)
break;
}
__atomic_store_n(&hdr->payloadReady, 0, __ATOMIC_RELEASE);
__atomic_store_n(&g_threadRunning, 0, __ATOMIC_RELEASE);
return nullptr;
}
/* ── init / cleanup ───────────────────────────────────────────────── */
static void payload_cleanup()
{
__atomic_store_n(&g_shutdown, 1, __ATOMIC_RELEASE);
/* wake the thread if blocked */
if (g_reqSem != SEM_FAILED) sem_post(g_reqSem);
if (__atomic_load_n(&g_threadRunning, __ATOMIC_ACQUIRE)) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 2;
pthread_timedjoin_np(g_thread, nullptr, &ts);
}
if (g_mappedView && g_mappedView != MAP_FAILED) {
munmap(g_mappedView, RCX_RPC_SHM_SIZE);
g_mappedView = nullptr;
}
if (g_shmFd >= 0) { close(g_shmFd); g_shmFd = -1; }
if (g_reqSem != SEM_FAILED) { sem_close(g_reqSem); g_reqSem = SEM_FAILED; }
if (g_rspSem != SEM_FAILED) { sem_close(g_rspSem); g_rspSem = SEM_FAILED; }
/* unlink named objects */
if (g_shmName[0]) shm_unlink(g_shmName);
if (g_reqName[0]) sem_unlink(g_reqName);
if (g_rspName[0]) sem_unlink(g_rspName);
if (g_memFd >= 0) { close(g_memFd); g_memFd = -1; }
}
__attribute__((constructor))
static void payload_init()
{
uint32_t pid = (uint32_t)getpid();
/* ── open /proc/self/mem for safe access ── */
g_memFd = open("/proc/self/mem", O_RDWR);
if (g_memFd < 0) return;
/* ── create main shared memory (PID-only naming) ── */
rcx_rpc_shm_name(g_shmName, sizeof(g_shmName), pid);
rcx_rpc_req_name(g_reqName, sizeof(g_reqName), pid);
rcx_rpc_rsp_name(g_rspName, sizeof(g_rspName), pid);
g_shmFd = shm_open(g_shmName, O_CREAT | O_RDWR, 0600);
if (g_shmFd < 0) return;
if (ftruncate(g_shmFd, RCX_RPC_SHM_SIZE) != 0) {
close(g_shmFd); g_shmFd = -1; return;
}
g_mappedView = mmap(nullptr, RCX_RPC_SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, g_shmFd, 0);
if (g_mappedView == MAP_FAILED) {
g_mappedView = nullptr;
close(g_shmFd); g_shmFd = -1;
return;
}
memset(g_mappedView, 0, RCX_RPC_HEADER_SIZE);
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
hdr->version = RCX_RPC_VERSION;
/* image base from /proc/self/maps: first executable mapping */
{
FILE* f = fopen("/proc/self/maps", "r");
if (f) {
char line[256];
while (fgets(line, sizeof(line), f)) {
uint64_t start;
char perms[8] = {};
if (sscanf(line, "%lx-%*x %7s", &start, perms) >= 2 && perms[2] == 'x') {
hdr->imageBase = start;
break;
}
}
fclose(f);
}
}
/* ── create semaphores ── */
g_reqSem = sem_open(g_reqName, O_CREAT, 0600, 0);
g_rspSem = sem_open(g_rspName, O_CREAT, 0600, 0);
if (g_reqSem == SEM_FAILED || g_rspSem == SEM_FAILED) {
payload_cleanup();
return;
}
/* ── start server thread (it will set payloadReady = 1) ── */
__atomic_store_n(&g_threadRunning, 1, __ATOMIC_RELEASE);
if (pthread_create(&g_thread, nullptr, server_thread_func, nullptr) != 0) {
__atomic_store_n(&g_threadRunning, 0, __ATOMIC_RELEASE);
payload_cleanup();
return;
}
pthread_detach(g_thread);
}
__attribute__((destructor))
static void payload_deinit()
{
payload_cleanup();
}
#endif /* _WIN32 / linux */

View File

@@ -0,0 +1,115 @@
/*
* RCX RPC Protocol -- shared between plugin DLL and payload DLL/SO.
* No dependencies beyond standard C headers.
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* ── constants ─────────────────────────────────────────────────────── */
#define RCX_RPC_VERSION 1
#define RCX_RPC_MAX_BATCH 256
#define RCX_RPC_SHM_SIZE (1024 * 1024) /* 1 MB */
#define RCX_RPC_HEADER_SIZE 4096
#define RCX_RPC_DATA_OFFSET RCX_RPC_HEADER_SIZE
#define RCX_RPC_DATA_SIZE (RCX_RPC_SHM_SIZE - RCX_RPC_DATA_OFFSET)
/* status codes */
#define RCX_RPC_STATUS_OK 0
#define RCX_RPC_STATUS_ERROR 1
#define RCX_RPC_STATUS_PARTIAL 2
/* ── commands ──────────────────────────────────────────────────────── */
#ifdef __cplusplus
enum RcxRpcCommand : uint32_t {
#else
typedef uint32_t RcxRpcCommand;
enum {
#endif
RPC_CMD_NONE = 0,
RPC_CMD_READ_BATCH = 1, /* batch read: N {address, length} pairs */
RPC_CMD_WRITE = 2, /* single write */
RPC_CMD_ENUM_MODULES = 3, /* enumerate loaded modules */
RPC_CMD_PING = 4, /* heartbeat */
RPC_CMD_SHUTDOWN = 5, /* graceful teardown */
};
/* ── wire structs (natural alignment, verified by static_assert) ─── */
struct RcxRpcReadEntry {
uint64_t address;
uint32_t length;
uint32_t dataOffset; /* offset into data region for response bytes */
};
struct RcxRpcModuleEntry {
uint64_t base;
uint64_t size;
uint32_t nameOffset; /* offset into data region, UTF-16 on Win, UTF-8 on Linux */
uint32_t nameLength; /* in bytes */
};
/*
* Header -- lives at shared-memory offset 0, padded to 4096 bytes.
*
* offset field
* ------ -----
* 0 version (4)
* 4 payloadReady (4)
* 8 command (4)
* 12 requestCount (4)
* 16 writeAddress (8)
* 24 writeLength (4)
* 28 status (4)
* 32 responseCount (4)
* 36 totalDataUsed (4)
* 40 imageBase (8) -- main module base from PEB / procfs
* 48 pointerSize (4) -- 4 for 32-bit, 8 for 64-bit payload
* 52 _pad[4044]
*/
struct RcxRpcHeader {
uint32_t version;
uint32_t payloadReady; /* payload sets to 1 after init */
uint32_t command; /* RcxRpcCommand */
uint32_t requestCount;
uint64_t writeAddress;
uint32_t writeLength;
uint32_t status; /* RCX_RPC_STATUS_* */
uint32_t responseCount;
uint32_t totalDataUsed;
uint64_t imageBase; /* main module base (PEB on Win, /proc on Linux) */
uint32_t pointerSize; /* 4 for 32-bit, 8 for 64-bit payload */
uint8_t _pad[RCX_RPC_HEADER_SIZE - 52];
};
/* ── name formatting helpers (PID-only, no nonce) ─────────────────── */
static inline void rcx_rpc_shm_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_SHM_%u", pid);
#else
snprintf(buf, n, "/rcx_shm_%u", pid);
#endif
}
static inline void rcx_rpc_req_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_REQ_%u", pid);
#else
snprintf(buf, n, "/rcx_req_%u", pid);
#endif
}
static inline void rcx_rpc_rsp_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_RSP_%u", pid);
#else
snprintf(buf, n, "/rcx_rsp_%u", pid);
#endif
}
#ifdef __cplusplus
static_assert(sizeof(RcxRpcHeader) == RCX_RPC_HEADER_SIZE, "Header must be 4096 bytes");
#endif

View File

@@ -0,0 +1,593 @@
/*
* test_rpc_client -- connects to a running test_rpc_host (or spawns one),
* exercises every RPC command, and benchmarks throughput.
*
* Usage:
* test_rpc_client (auto-spawn host)
* test_rpc_client <pid> [testbuf_hex testlen]
*/
#include "../rcx_rpc_protocol.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <chrono>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <unistd.h>
# include <fcntl.h>
# include <sys/mman.h>
# include <semaphore.h>
# include <libgen.h>
# include <limits.h>
#endif
/* ══════════════════════════════════════════════════════════════════════
* Minimal standalone IPC client (no Qt, mirrors plugin's IpcClient)
* ══════════════════════════════════════════════════════════════════════ */
struct TestIpcClient {
#ifdef _WIN32
HANDLE hShm = nullptr;
HANDLE hReqEvent = nullptr;
HANDLE hRspEvent = nullptr;
#else
int shmFd = -1;
sem_t* reqSem = SEM_FAILED;
sem_t* rspSem = SEM_FAILED;
#endif
void* view = nullptr;
bool ok = false;
bool connect(uint32_t pid, int timeoutMs = 5000)
{
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32
ULONGLONG deadline = GetTickCount64() + (ULONGLONG)timeoutMs;
while (!(hShm = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shmName))) {
if (GetTickCount64() >= deadline) return false;
Sleep(10);
}
view = MapViewOfFile(hShm, FILE_MAP_ALL_ACCESS, 0, 0, RCX_RPC_SHM_SIZE);
if (!view) { CloseHandle(hShm); hShm = nullptr; return false; }
hReqEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, reqName);
hRspEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, rspName);
if (!hReqEvent || !hRspEvent) return false;
#else
auto start = std::chrono::steady_clock::now();
while (true) {
shmFd = shm_open(shmName, O_RDWR, 0);
if (shmFd >= 0) break;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed >= timeoutMs) return false;
usleep(10000);
}
view = mmap(nullptr, RCX_RPC_SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, shmFd, 0);
if (view == MAP_FAILED) { view = nullptr; close(shmFd); shmFd = -1; return false; }
reqSem = sem_open(reqName, 0);
rspSem = sem_open(rspName, 0);
if (reqSem == SEM_FAILED || rspSem == SEM_FAILED) return false;
#endif
/* wait for payloadReady */
auto* hdr = (RcxRpcHeader*)view;
#ifdef _WIN32
while (!hdr->payloadReady) {
if (GetTickCount64() >= deadline) return false;
Sleep(5);
}
#else
while (!__atomic_load_n(&hdr->payloadReady, __ATOMIC_ACQUIRE)) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed >= timeoutMs) return false;
usleep(5000);
}
#endif
ok = true;
return true;
}
void disconnect()
{
#ifdef _WIN32
if (view) { UnmapViewOfFile(view); view = nullptr; }
if (hShm) { CloseHandle(hShm); hShm = nullptr; }
if (hReqEvent) { CloseHandle(hReqEvent); hReqEvent = nullptr; }
if (hRspEvent) { CloseHandle(hRspEvent); hRspEvent = nullptr; }
#else
if (view) { munmap(view, RCX_RPC_SHM_SIZE); view = nullptr; }
if (shmFd >= 0) { close(shmFd); shmFd = -1; }
if (reqSem != SEM_FAILED) { sem_close(reqSem); reqSem = SEM_FAILED; }
if (rspSem != SEM_FAILED) { sem_close(rspSem); rspSem = SEM_FAILED; }
#endif
ok = false;
}
bool signalAndWait(int timeoutMs = 2000)
{
#ifdef _WIN32
SetEvent(hReqEvent);
return WaitForSingleObject(hRspEvent, (DWORD)timeoutMs) == WAIT_OBJECT_0;
#else
sem_post(reqSem);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeoutMs / 1000;
ts.tv_nsec += (timeoutMs % 1000) * 1000000L;
if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
return sem_timedwait(rspSem, &ts) == 0;
#endif
}
/* ── RPC helpers ──────────────────────────────────────────────── */
bool rpc_ping()
{
auto* hdr = (RcxRpcHeader*)view;
hdr->command = RPC_CMD_PING;
hdr->status = RCX_RPC_STATUS_OK;
return signalAndWait();
}
bool rpc_read(uint64_t addr, void* buf, uint32_t len)
{
auto* hdr = (RcxRpcHeader*)view;
auto* data = (uint8_t*)view + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_READ_BATCH;
hdr->requestCount = 1;
hdr->status = RCX_RPC_STATUS_OK;
auto* entry = (RcxRpcReadEntry*)data;
entry->address = addr;
entry->length = len;
entry->dataOffset = sizeof(RcxRpcReadEntry);
if (!signalAndWait()) return false;
memcpy(buf, data + entry->dataOffset, len);
return true;
}
bool rpc_read_batch(const uint64_t* addrs, const uint32_t* lens,
uint32_t count, uint8_t* outBuf)
{
auto* hdr = (RcxRpcHeader*)view;
auto* data = (uint8_t*)view + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_READ_BATCH;
hdr->requestCount = count;
hdr->status = RCX_RPC_STATUS_OK;
/* lay out entries, then data offsets after all entries */
uint32_t entriesSize = count * (uint32_t)sizeof(RcxRpcReadEntry);
uint32_t dataOff = entriesSize;
for (uint32_t i = 0; i < count; ++i) {
auto* e = (RcxRpcReadEntry*)(data + i * sizeof(RcxRpcReadEntry));
e->address = addrs[i];
e->length = lens[i];
e->dataOffset = dataOff;
dataOff += lens[i];
}
if (!signalAndWait()) return false;
/* copy out response data */
uint32_t off = 0;
for (uint32_t i = 0; i < count; ++i) {
auto* e = (RcxRpcReadEntry*)(data + i * sizeof(RcxRpcReadEntry));
memcpy(outBuf + off, data + e->dataOffset, e->length);
off += e->length;
}
return true;
}
bool rpc_write(uint64_t addr, const void* buf, uint32_t len)
{
auto* hdr = (RcxRpcHeader*)view;
auto* data = (uint8_t*)view + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_WRITE;
hdr->writeAddress = addr;
hdr->writeLength = len;
hdr->status = RCX_RPC_STATUS_OK;
memcpy(data, buf, len);
if (!signalAndWait()) return false;
return hdr->status == RCX_RPC_STATUS_OK;
}
struct ModInfo { uint64_t base; uint64_t size; char name[256]; };
int rpc_enum_modules(ModInfo* out, int maxOut)
{
auto* hdr = (RcxRpcHeader*)view;
auto* data = (uint8_t*)view + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_ENUM_MODULES;
hdr->status = RCX_RPC_STATUS_OK;
if (!signalAndWait()) return -1;
if (hdr->status != RCX_RPC_STATUS_OK) return -1;
int count = (int)hdr->responseCount;
if (count > maxOut) count = maxOut;
for (int i = 0; i < count; ++i) {
auto* entry = (RcxRpcModuleEntry*)(data + i * sizeof(RcxRpcModuleEntry));
out[i].base = entry->base;
out[i].size = entry->size;
#ifdef _WIN32
/* names are UTF-16 on Windows */
int wchars = (int)(entry->nameLength / sizeof(wchar_t));
WideCharToMultiByte(CP_UTF8, 0,
(const wchar_t*)(data + entry->nameOffset), wchars,
out[i].name, 255, nullptr, nullptr);
out[i].name[255] = '\0';
#else
int nLen = (int)entry->nameLength;
if (nLen > 255) nLen = 255;
memcpy(out[i].name, data + entry->nameOffset, nLen);
out[i].name[nLen] = '\0';
#endif
}
return count;
}
void rpc_shutdown()
{
auto* hdr = (RcxRpcHeader*)view;
hdr->command = RPC_CMD_SHUTDOWN;
hdr->status = RCX_RPC_STATUS_OK;
signalAndWait(500);
}
};
/* ══════════════════════════════════════════════════════════════════════
* Auto-spawn host
* ══════════════════════════════════════════════════════════════════════ */
#ifdef _WIN32
static HANDLE g_hostProcess = nullptr;
#else
static pid_t g_hostPid = 0;
#endif
static FILE* g_hostPipe = nullptr;
static bool spawn_host(uint32_t* outPid,
uint64_t* outTestBuf, uint32_t* outTestLen)
{
/* resolve path to test_rpc_host next to ourselves */
char cmd[2048];
#ifdef _WIN32
char exePath[MAX_PATH];
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
char* slash = strrchr(exePath, '\\');
if (!slash) slash = strrchr(exePath, '/');
if (slash) *(slash + 1) = '\0';
snprintf(cmd, sizeof(cmd), "\"%stest_rpc_host.exe\" autotest", exePath);
g_hostPipe = _popen(cmd, "r");
#else
char exePath[PATH_MAX];
ssize_t n = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
if (n <= 0) return false;
exePath[n] = '\0';
char* dir = dirname(exePath);
snprintf(cmd, sizeof(cmd), "%s/test_rpc_host autotest", dir);
g_hostPipe = popen(cmd, "r");
#endif
if (!g_hostPipe) {
fprintf(stderr, "ERROR: cannot spawn host: %s\n", cmd);
return false;
}
/* read READY line */
char line[512];
if (!fgets(line, sizeof(line), g_hostPipe)) {
fprintf(stderr, "ERROR: no output from host\n");
return false;
}
/* parse: READY pid=X testbuf=0xZ testlen=N */
unsigned long long tbuf = 0;
unsigned tlen = 0;
if (sscanf(line, "READY pid=%u testbuf=0x%llx testlen=%u",
outPid, &tbuf, &tlen) < 1) {
fprintf(stderr, "ERROR: cannot parse host output: %s\n", line);
return false;
}
*outTestBuf = (uint64_t)tbuf;
*outTestLen = (uint32_t)tlen;
return true;
}
static void cleanup_host()
{
if (g_hostPipe) {
#ifdef _WIN32
_pclose(g_hostPipe);
#else
pclose(g_hostPipe);
#endif
g_hostPipe = nullptr;
}
}
/* ══════════════════════════════════════════════════════════════════════
* Printing helpers
* ══════════════════════════════════════════════════════════════════════ */
static void print_pass(const char* name) { printf(" [PASS] %s\n", name); }
static void print_fail(const char* name) { printf(" [FAIL] %s\n", name); exit(1); }
/* ══════════════════════════════════════════════════════════════════════
* main
* ══════════════════════════════════════════════════════════════════════ */
int main(int argc, char** argv)
{
uint32_t pid = 0;
uint64_t testBuf = 0;
uint32_t testLen = 0;
bool autoMode = false;
if (argc >= 2) {
pid = (uint32_t)atoi(argv[1]);
if (argc >= 4) {
testBuf = (uint64_t)strtoull(argv[2], nullptr, 0);
testLen = (uint32_t)atoi(argv[3]);
}
} else {
autoMode = true;
printf("Auto-spawning test_rpc_host...\n");
if (!spawn_host(&pid, &testBuf, &testLen)) return 1;
}
printf("Connecting to PID=%u testbuf=0x%llx testlen=%u\n\n",
pid, (unsigned long long)testBuf, testLen);
/* ── connect ── */
TestIpcClient ipc;
if (!ipc.connect(pid)) {
fprintf(stderr, "ERROR: IPC connect failed\n");
if (autoMode) cleanup_host();
return 1;
}
printf("=== Functional Tests ===\n");
/* ── test: ping ── */
if (ipc.rpc_ping()) print_pass("Ping");
else print_fail("Ping");
/* ── test: enumerate modules ── */
TestIpcClient::ModInfo mods[512];
int modCount = ipc.rpc_enum_modules(mods, 512);
if (modCount > 0) {
printf(" [PASS] EnumModules (%d modules)\n", modCount);
printf(" first: %s base=0x%llx size=0x%llx\n",
mods[0].name,
(unsigned long long)mods[0].base,
(unsigned long long)mods[0].size);
} else {
print_fail("EnumModules");
}
/* ── test: read module header (MZ / ELF magic) ── */
if (modCount > 0) {
uint8_t header[4] = {};
if (ipc.rpc_read(mods[0].base, header, 4)) {
#ifdef _WIN32
if (header[0] == 'M' && header[1] == 'Z')
print_pass("ReadModuleHeader (MZ)");
else
print_fail("ReadModuleHeader (expected MZ)");
#else
if (header[0] == 0x7F && header[1] == 'E' &&
header[2] == 'L' && header[3] == 'F')
print_pass("ReadModuleHeader (ELF)");
else
print_fail("ReadModuleHeader (expected ELF)");
#endif
} else {
print_fail("ReadModuleHeader (read failed)");
}
}
/* ── test: read test buffer (known pattern) ── */
if (testBuf && testLen >= 4096) {
uint8_t buf[4096];
if (ipc.rpc_read(testBuf, buf, 4096)) {
bool good = true;
for (int i = 0; i < 4096; ++i) {
if (buf[i] != (uint8_t)(i & 0xFF)) { good = false; break; }
}
if (good) print_pass("ReadTestBuffer (4096 bytes, pattern verified)");
else print_fail("ReadTestBuffer (pattern mismatch)");
} else {
print_fail("ReadTestBuffer (read failed)");
}
}
/* ── test: write ── */
if (testBuf && testLen >= 16) {
uint8_t patch[4] = {0xDE, 0xAD, 0xBE, 0xEF};
if (ipc.rpc_write(testBuf, patch, 4)) {
uint8_t verify[4] = {};
ipc.rpc_read(testBuf, verify, 4);
if (memcmp(verify, patch, 4) == 0)
print_pass("Write + ReadBack (0xDEADBEEF)");
else
print_fail("Write + ReadBack (readback mismatch)");
} else {
print_fail("Write (write failed)");
}
}
/* ── test: batch read ── */
if (testBuf && testLen >= 8192) {
const uint32_t N = 4;
uint64_t addrs[N];
uint32_t lens[N];
for (uint32_t i = 0; i < N; ++i) {
addrs[i] = testBuf + i * 1024;
lens[i] = 1024;
}
uint8_t out[4096];
if (ipc.rpc_read_batch(addrs, lens, N, out)) {
print_pass("BatchRead (4 x 1024 bytes)");
} else {
print_fail("BatchRead");
}
}
printf("\n=== Benchmarks ===\n");
/* choose a valid address for benchmarking */
uint64_t benchAddr = testBuf ? testBuf : (modCount > 0 ? mods[0].base : 0);
if (!benchAddr) {
printf(" (no valid address for benchmarks, skipping)\n");
} else {
/* ── benchmark: single 4 KB reads ── */
{
const int ITERS = 10000;
const int PAGE = 4096;
uint8_t tmp[4096];
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i)
ipc.rpc_read(benchAddr, tmp, PAGE);
auto t1 = std::chrono::high_resolution_clock::now();
double us = (double)std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
double secs = us / 1e6;
double totalMB = (double)ITERS * PAGE / (1024.0 * 1024.0);
printf(" Single 4 KB reads:\n");
printf(" Iterations : %d\n", ITERS);
printf(" Total data : %.2f MB\n", totalMB);
printf(" Wall time : %.3f s\n", secs);
printf(" Throughput : %.2f MB/s\n", totalMB / secs);
printf(" Avg latency: %.2f us/read\n", us / ITERS);
}
/* ── benchmark: single 64 B reads (pointer-chase-size) ── */
{
const int ITERS = 50000;
const int SZ = 64;
uint8_t tmp[64];
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i)
ipc.rpc_read(benchAddr, tmp, SZ);
auto t1 = std::chrono::high_resolution_clock::now();
double us = (double)std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
double secs = us / 1e6;
double totalKB = (double)ITERS * SZ / 1024.0;
printf(" Single 64 B reads (pointer-chase):\n");
printf(" Iterations : %d\n", ITERS);
printf(" Total data : %.2f KB\n", totalKB);
printf(" Wall time : %.3f s\n", secs);
printf(" Throughput : %.2f KB/s\n", totalKB / secs);
printf(" Avg latency: %.2f us/read\n", us / ITERS);
}
/* ── benchmark: batch read (50 x 4 KB, simulating refresh) ── */
{
const int ITERS = 2000;
const uint32_t BATCH = 50;
const uint32_t PAGE = 4096;
uint64_t addrs[BATCH];
uint32_t lens[BATCH];
for (uint32_t i = 0; i < BATCH; ++i) {
/* wrap within test buffer or module */
addrs[i] = benchAddr + (i * PAGE) % 65536;
lens[i] = PAGE;
}
/* allocate response buffer */
uint8_t* outBuf = (uint8_t*)malloc(BATCH * PAGE);
if (!outBuf) {
printf(" (batch malloc failed, skipping)\n");
} else {
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i)
ipc.rpc_read_batch(addrs, lens, BATCH, outBuf);
auto t1 = std::chrono::high_resolution_clock::now();
double us = (double)std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
double secs = us / 1e6;
double totalMB = (double)ITERS * BATCH * PAGE / (1024.0 * 1024.0);
printf(" Batch read (%u x %u B, simulating refresh):\n", BATCH, PAGE);
printf(" Iterations : %d\n", ITERS);
printf(" Total data : %.2f MB\n", totalMB);
printf(" Wall time : %.3f s\n", secs);
printf(" Throughput : %.2f MB/s\n", totalMB / secs);
printf(" Avg latency: %.2f us/batch\n", us / ITERS);
printf(" Per-page : %.2f us/page\n", us / (ITERS * BATCH));
free(outBuf);
}
}
/* ── benchmark: write 4 KB ── */
if (testBuf && testLen >= 4096) {
const int ITERS = 10000;
const int PAGE = 4096;
uint8_t tmp[4096];
memset(tmp, 0x42, sizeof(tmp));
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i)
ipc.rpc_write(testBuf, tmp, PAGE);
auto t1 = std::chrono::high_resolution_clock::now();
double us = (double)std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
double secs = us / 1e6;
double totalMB = (double)ITERS * PAGE / (1024.0 * 1024.0);
printf(" Write 4 KB:\n");
printf(" Iterations : %d\n", ITERS);
printf(" Total data : %.2f MB\n", totalMB);
printf(" Wall time : %.3f s\n", secs);
printf(" Throughput : %.2f MB/s\n", totalMB / secs);
printf(" Avg latency: %.2f us/write\n", us / ITERS);
}
}
/* ── shutdown ── */
printf("\nSending shutdown...\n");
ipc.rpc_shutdown();
ipc.disconnect();
if (autoMode) {
/* wait for host to exit */
#ifdef _WIN32
Sleep(500);
#else
usleep(500000);
#endif
cleanup_host();
}
printf("Done.\n");
return 0;
}

View File

@@ -0,0 +1,187 @@
/*
* test_rpc_host -- loads rcx_payload in-process, acts as the "target".
*
* Usage: test_rpc_host
*
* Prints a READY line (machine-parseable), then waits for the payload
* to shut down (RPC_CMD_SHUTDOWN from the client).
*/
#include "../rcx_rpc_protocol.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <unistd.h>
# include <dlfcn.h>
# include <fcntl.h>
# include <sys/mman.h>
# include <semaphore.h>
# include <libgen.h>
# include <limits.h>
#endif
/* ── Helpers ──────────────────────────────────────────────────────── */
static uint32_t current_pid()
{
#ifdef _WIN32
return (uint32_t)GetCurrentProcessId();
#else
return (uint32_t)getpid();
#endif
}
static void sleep_ms(int ms)
{
#ifdef _WIN32
Sleep((DWORD)ms);
#else
usleep((useconds_t)ms * 1000);
#endif
}
/* Resolve payload path relative to this executable */
static int payload_path(char* out, int outLen)
{
#ifdef _WIN32
char exePath[MAX_PATH];
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
char* slash = strrchr(exePath, '\\');
if (!slash) slash = strrchr(exePath, '/');
if (slash) *(slash + 1) = '\0';
snprintf(out, outLen, "%srcx_payload.dll", exePath);
#else
char exePath[PATH_MAX];
ssize_t n = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
if (n <= 0) return -1;
exePath[n] = '\0';
char* dir = dirname(exePath);
snprintf(out, outLen, "%s/rcx_payload.so", dir);
#endif
return 0;
}
/* Open the main shared memory (read-only, just to monitor payloadReady) */
static void* open_main_shm(uint32_t pid)
{
char shmName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
#ifdef _WIN32
HANDLE h = nullptr;
for (int i = 0; i < 500; ++i) {
h = OpenFileMappingA(FILE_MAP_READ, FALSE, shmName);
if (h) break;
sleep_ms(10);
}
if (!h) return nullptr;
void* v = MapViewOfFile(h, FILE_MAP_READ, 0, 0, sizeof(RcxRpcHeader));
return v;
#else
int fd = -1;
for (int i = 0; i < 500; ++i) {
fd = shm_open(shmName, O_RDONLY, 0);
if (fd >= 0) break;
sleep_ms(10);
}
if (fd < 0) return nullptr;
void* v = mmap(nullptr, sizeof(RcxRpcHeader), PROT_READ, MAP_SHARED, fd, 0);
close(fd);
return (v == MAP_FAILED) ? nullptr : v;
#endif
}
/* ── Test buffer (known pattern for client to verify reads/writes) ── */
static uint8_t g_testBuf[65536];
/* ── main ─────────────────────────────────────────────────────────── */
int main(int, char**)
{
uint32_t pid = current_pid();
/* fill test buffer with known pattern */
for (int i = 0; i < (int)sizeof(g_testBuf); ++i)
g_testBuf[i] = (uint8_t)(i & 0xFF);
/* load payload */
char plPath[1024];
if (payload_path(plPath, sizeof(plPath)) != 0) {
fprintf(stderr, "ERROR: cannot determine payload path\n");
return 1;
}
#ifdef _WIN32
HMODULE hPayload = LoadLibraryA(plPath);
if (!hPayload) {
fprintf(stderr, "ERROR: LoadLibrary(%s) failed (%lu)\n",
plPath, GetLastError());
return 1;
}
/* Call RcxPayloadInit() — DllMain is minimal, init must be explicit */
typedef bool (*RcxPayloadInitFn)();
auto pfnInit = (RcxPayloadInitFn)GetProcAddress(hPayload, "RcxPayloadInit");
if (!pfnInit || !pfnInit()) {
fprintf(stderr, "ERROR: RcxPayloadInit() failed or not found\n");
FreeLibrary(hPayload);
return 1;
}
#else
void* hPayload = dlopen(plPath, RTLD_NOW);
if (!hPayload) {
fprintf(stderr, "ERROR: dlopen(%s): %s\n", plPath, dlerror());
return 1;
}
#endif
/* open main shm and wait for payloadReady */
void* shmView = open_main_shm(pid);
if (!shmView) {
fprintf(stderr, "ERROR: failed to open main shared memory\n");
return 1;
}
RcxRpcHeader* hdr = (RcxRpcHeader*)shmView;
for (int i = 0; i < 500; ++i) {
if (hdr->payloadReady) break;
sleep_ms(10);
}
if (!hdr->payloadReady) {
fprintf(stderr, "ERROR: payload did not become ready\n");
return 1;
}
/* print READY line for the client to parse */
printf("READY pid=%u testbuf=0x%llx testlen=%u\n",
pid,
(unsigned long long)(uintptr_t)g_testBuf,
(unsigned)sizeof(g_testBuf));
fflush(stdout);
/* wait until payload shuts down */
while (hdr->payloadReady)
sleep_ms(100);
printf("Payload shut down, exiting.\n");
#ifdef _WIN32
/* give the timer queue a moment to drain */
Sleep(200);
FreeLibrary(hPayload);
if (shmView) UnmapViewOfFile(shmView);
#else
usleep(200000);
dlclose(hPayload);
if (shmView) munmap(shmView, sizeof(RcxRpcHeader));
#endif
return 0;
}

View File

@@ -20,7 +20,7 @@ set(PLUGIN_SOURCES
add_library(WinDbgMemoryPlugin SHARED ${PLUGIN_SOURCES})
# Link Qt + DbgEng
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets dbgeng ole32)
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets ole32)
# Include directories
target_include_directories(WinDbgMemoryPlugin PRIVATE

View File

@@ -12,12 +12,99 @@
#include <QDebug>
#include <QClipboard>
#include <QGuiApplication>
#include <QFileDialog>
#include <QFileInfo>
#include <QSettings>
#ifdef _WIN32
#include <windows.h>
#include <initguid.h>
#include <dbgeng.h>
#pragma comment(lib, "dbgeng.lib")
// dbgeng.dll is loaded dynamically — see loadDbgEngTools()
// The system dbgeng.dll (C:\Windows\System32) does not support remote
// connections (DebugConnect returns 0x8007053d). The full version lives
// in the Debugging Tools for Windows directory. We load it dynamically
// so the plugin works without requiring the debugger tools on PATH.
static const char* const kDbgToolsDirs[] = {
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64",
"C:\\Program Files\\Windows Kits\\10\\Debuggers\\x64",
};
static const char* const kSettingsKey = "WinDbgPlugin/DbgToolsDir";
typedef HRESULT (STDAPICALLTYPE *PFN_DebugConnect)(PCSTR, REFIID, PVOID*);
typedef HRESULT (STDAPICALLTYPE *PFN_DebugCreate)(REFIID, PVOID*);
static QString s_loadedDir;
static HMODULE s_hDbgEng = nullptr;
static HMODULE tryLoadFrom(const char* dir) {
SetDllDirectoryA(dir);
// Pre-load dependencies so the tools versions are used instead of
// the older System32 copies (e.g. dbghelp.dll without StackWalk2).
char path[MAX_PATH];
for (auto dep : {"dbghelp.dll", "dbgcore.dll", "symsrv.dll"}) {
snprintf(path, sizeof(path), "%s\\%s", dir, dep);
LoadLibraryA(path); // OK if missing
}
snprintf(path, sizeof(path), "%s\\dbgeng.dll", dir);
HMODULE h = LoadLibraryA(path);
if (h) {
s_loadedDir = QString::fromLocal8Bit(dir);
qDebug() << "[WinDbg] Loaded dbgeng.dll from" << dir;
}
return h;
}
static HMODULE loadDbgEngTools() {
if (s_hDbgEng) return s_hDbgEng;
// 1. Try user-configured path from settings
QSettings settings;
QString userDir = settings.value(kSettingsKey).toString();
if (!userDir.isEmpty()) {
s_hDbgEng = tryLoadFrom(userDir.toLocal8Bit().constData());
if (s_hDbgEng) return s_hDbgEng;
}
// 2. Try well-known install paths
for (auto dir : kDbgToolsDirs) {
s_hDbgEng = tryLoadFrom(dir);
if (s_hDbgEng) return s_hDbgEng;
}
SetDllDirectoryA(nullptr);
return nullptr;
}
static bool dbgToolsFound() {
loadDbgEngTools();
return s_hDbgEng != nullptr;
}
static PFN_DebugConnect getDebugConnect() {
static PFN_DebugConnect pfn = nullptr;
static bool tried = false;
if (!tried) {
tried = true;
HMODULE h = loadDbgEngTools();
if (h) pfn = (PFN_DebugConnect)GetProcAddress(h, "DebugConnect");
if (!pfn) qWarning() << "[WinDbg] DebugConnect not available — Debugging Tools not found";
}
return pfn;
}
static PFN_DebugCreate getDebugCreate() {
static PFN_DebugCreate pfn = nullptr;
static bool tried = false;
if (!tried) {
tried = true;
HMODULE h = loadDbgEngTools();
if (h) pfn = (PFN_DebugCreate)GetProcAddress(h, "DebugCreate");
if (!pfn) qWarning() << "[WinDbg] DebugCreate not available — Debugging Tools not found";
}
return pfn;
}
#endif
// ──────────────────────────────────────────────────────────────────────────
@@ -65,6 +152,9 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
dispatchToOwner([this, &target]() {
HRESULT hr;
// COM must be initialized on this thread for DbgEng
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
qDebug() << "[WinDbg] Opening target:" << target
<< "on DbgEng thread" << QThread::currentThread();
@@ -72,9 +162,11 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|| target.startsWith("npipe:", Qt::CaseInsensitive))
{
// ── Remote: connect to existing WinDbg debug server ──
auto pfnConnect = getDebugConnect();
if (!pfnConnect) { qWarning() << "[WinDbg] Debugging Tools required for remote connections"; return; }
QByteArray connUtf8 = target.toUtf8();
qDebug() << "[WinDbg] DebugConnect:" << target;
hr = DebugConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
hr = pfnConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
qDebug() << "[WinDbg] DebugConnect hr=" << Qt::hex << (unsigned long)hr
<< "client=" << (void*)m_client;
if (FAILED(hr) || !m_client) {
@@ -86,7 +178,9 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
else
{
// ── Local: create debug client for pid/dump ──
hr = DebugCreate(IID_IDebugClient, (void**)&m_client);
auto pfnCreate = getDebugCreate();
if (!pfnCreate) { qWarning() << "[WinDbg] Debugging Tools required"; return; }
hr = pfnCreate(IID_IDebugClient, (void**)&m_client);
qDebug() << "[WinDbg] DebugCreate hr=" << Qt::hex << (unsigned long)hr
<< "client=" << (void*)m_client;
if (FAILED(hr) || !m_client) {
@@ -165,6 +259,10 @@ void WinDbgMemoryProvider::initInterfaces()
qDebug() << "[WinDbg] IDebugDataSpaces hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_dataSpaces;
hr = m_client->QueryInterface(IID_IDebugDataSpaces2, (void**)&m_dataSpaces2);
qDebug() << "[WinDbg] IDebugDataSpaces2 hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_dataSpaces2;
hr = m_client->QueryInterface(IID_IDebugControl, (void**)&m_control);
qDebug() << "[WinDbg] IDebugControl hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_control;
@@ -197,53 +295,28 @@ void WinDbgMemoryProvider::querySessionInfo()
}
}
if (m_symbols) {
ULONG numModules = 0, numUnloaded = 0;
hr = m_symbols->GetNumberModules(&numModules, &numUnloaded);
qDebug() << "[WinDbg] GetNumberModules hr=" << Qt::hex << (unsigned long)hr
<< "loaded=" << numModules << "unloaded=" << numUnloaded;
if (SUCCEEDED(hr) && numModules > 0) {
char modName[256] = {};
ULONG modSize = 0;
hr = m_symbols->GetModuleNames(0, 0, nullptr, 0, nullptr,
modName, sizeof(modName), &modSize,
nullptr, 0, nullptr);
if (SUCCEEDED(hr) && modSize > 0)
m_name = QString::fromUtf8(modName);
// Query effective processor type for pointer size detection
if (m_control) {
ULONG procType = 0;
hr = m_control->GetEffectiveProcessorType(&procType);
if (SUCCEEDED(hr)) {
// IMAGE_FILE_MACHINE_I386 = 0x014C
if (procType == 0x014C)
m_pointerSize = 4;
qDebug() << "[WinDbg] EffectiveProcessorType=" << Qt::hex << procType
<< "pointerSize=" << m_pointerSize;
}
}
if (m_name.isEmpty())
m_name = m_isLive ? QStringLiteral("DbgEng (Live)") : QStringLiteral("DbgEng (Dump)");
if (m_symbols) {
ULONG numModules = 0, numUnloaded = 0;
hr = m_symbols->GetNumberModules(&numModules, &numUnloaded);
if (SUCCEEDED(hr) && numModules > 0) {
ULONG64 moduleBase = 0;
hr = m_symbols->GetModuleByIndex(0, &moduleBase);
qDebug() << "[WinDbg] Module 0 base=" << Qt::hex << moduleBase;
if (SUCCEEDED(hr))
m_base = moduleBase;
}
}
if (m_base && m_dataSpaces) {
uint8_t probe[2] = {};
ULONG got = 0;
hr = m_dataSpaces->ReadVirtual(m_base, probe, 2, &got);
qDebug() << "[WinDbg] Probe read at" << Qt::hex << m_base
<< "hr=" << (unsigned long)hr << "got=" << got
<< "bytes:" << (int)probe[0] << (int)probe[1];
if (FAILED(hr) || got == 0) {
qWarning() << "[WinDbg] Probe read FAILED — cleaning up";
cleanup();
return;
}
}
// WinDbg provides access to the entire virtual address space.
// Do NOT auto-select a module as base — let the user set their
// own base address. m_base stays 0 so the controller won't
// override tree.baseAddress.
m_name = m_isLive ? QStringLiteral("WinDbg (Live)")
: QStringLiteral("WinDbg (Dump)");
qDebug() << "[WinDbg] Ready. name=" << m_name
<< "base=" << Qt::hex << m_base << "isLive=" << m_isLive;
<< "isLive=" << m_isLive;
#endif
}
@@ -260,6 +333,7 @@ WinDbgMemoryProvider::~WinDbgMemoryProvider()
m_client->DetachProcesses();
}
cleanup();
CoUninitialize();
});
} else {
// Thread not running — clean up directly (best-effort)
@@ -289,10 +363,11 @@ WinDbgMemoryProvider::~WinDbgMemoryProvider()
void WinDbgMemoryProvider::cleanup()
{
#ifdef _WIN32
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
if (m_control) { m_control->Release(); m_control = nullptr; }
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
if (m_client) { m_client->Release(); m_client = nullptr; }
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
if (m_control) { m_control->Release(); m_control = nullptr; }
if (m_dataSpaces2) { m_dataSpaces2->Release(); m_dataSpaces2 = nullptr; }
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
if (m_client) { m_client->Release(); m_client = nullptr; }
#endif
}
@@ -304,9 +379,19 @@ bool WinDbgMemoryProvider::read(uint64_t addr, void* buf, int len) const
bool result = false;
dispatchToOwner([&]() {
ULONG bytesRead = 0;
HRESULT hr = m_dataSpaces->ReadVirtual(m_base + addr, buf, (ULONG)len, &bytesRead);
if (FAILED(hr) || (int)bytesRead < len)
memset((char*)buf + bytesRead, 0, len - bytesRead);
HRESULT hr = m_dataSpaces->ReadVirtual(addr, buf, (ULONG)len, &bytesRead);
if (SUCCEEDED(hr) && (int)bytesRead >= len) {
result = true;
return;
}
// Partial or failed read — zero-fill remainder and log
memset((char*)buf + bytesRead, 0, len - bytesRead);
++m_readFailCount;
if (m_readFailCount <= 5 || (m_readFailCount % 100) == 0)
qDebug() << "[WinDbg] ReadVirtual FAILED addr=0x" << Qt::hex << addr
<< "len=" << Qt::dec << len
<< "hr=0x" << Qt::hex << (unsigned long)hr
<< "got=" << Qt::dec << bytesRead;
result = bytesRead > 0;
});
return result;
@@ -324,7 +409,7 @@ bool WinDbgMemoryProvider::write(uint64_t addr, const void* buf, int len)
bool result = false;
dispatchToOwner([&]() {
ULONG bytesWritten = 0;
HRESULT hr = m_dataSpaces->WriteVirtual(m_base + addr, const_cast<void*>(buf),
HRESULT hr = m_dataSpaces->WriteVirtual(addr, const_cast<void*>(buf),
(ULONG)len, &bytesWritten);
result = SUCCEEDED(hr) && bytesWritten == (ULONG)len;
});
@@ -364,7 +449,7 @@ QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
char nameBuf[512] = {};
ULONG nameSize = 0;
ULONG64 displacement = 0;
HRESULT hr = m_symbols->GetNameByOffset(m_base + addr, nameBuf, sizeof(nameBuf),
HRESULT hr = m_symbols->GetNameByOffset(addr, nameBuf, sizeof(nameBuf),
&nameSize, &displacement);
if (SUCCEEDED(hr) && nameSize > 0) {
result = QString::fromUtf8(nameBuf);
@@ -379,6 +464,112 @@ QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
#endif
}
QVector<rcx::MemoryRegion> WinDbgMemoryProvider::enumerateRegions() const
{
QVector<rcx::MemoryRegion> regions;
#ifdef _WIN32
if (!m_dataSpaces) return regions;
// Enumerate modules — used for tagging (user-mode) or as the primary
// source of regions (kernel-mode, where QueryVirtual is unavailable).
struct ModInfo { uint64_t base; uint64_t size; QString name; };
QVector<ModInfo> modules;
if (m_symbols) {
dispatchToOwner([&]() {
ULONG loaded = 0, unloaded = 0;
if (FAILED(m_symbols->GetNumberModules(&loaded, &unloaded)))
return;
for (ULONG i = 0; i < loaded; i++) {
ULONG64 modBase = 0;
if (FAILED(m_symbols->GetModuleByIndex(i, &modBase)))
continue;
DEBUG_MODULE_PARAMETERS params = {};
if (FAILED(m_symbols->GetModuleParameters(1, &modBase, 0, &params)))
continue;
char nameBuf[256] = {};
ULONG nameSize = 0;
m_symbols->GetModuleNames(i, 0,
nullptr, 0, nullptr,
nameBuf, sizeof(nameBuf), &nameSize,
nullptr, 0, nullptr);
ModInfo mi;
mi.base = modBase;
mi.size = params.Size;
mi.name = QString::fromUtf8(nameBuf);
modules.append(mi);
}
});
}
// Try QueryVirtual first (user-mode debugging / user-mode dumps).
// MSDN: "This method is not available in kernel-mode debugging."
if (m_dataSpaces2) {
dispatchToOwner([&]() {
ULONG64 addr = 0;
int safety = 0;
constexpr int kMaxRegions = 500000;
while (safety++ < kMaxRegions) {
MEMORY_BASIC_INFORMATION64 mbi = {};
HRESULT hr = m_dataSpaces2->QueryVirtual(addr, &mbi);
if (FAILED(hr))
break;
if (mbi.State == MEM_COMMIT &&
!(mbi.Protect & PAGE_NOACCESS) &&
!(mbi.Protect & PAGE_GUARD))
{
rcx::MemoryRegion region;
region.base = mbi.BaseAddress;
region.size = mbi.RegionSize;
region.readable = true;
region.writable = (mbi.Protect & PAGE_READWRITE) ||
(mbi.Protect & PAGE_WRITECOPY) ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
(mbi.Protect & PAGE_EXECUTE_READ) ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
for (const auto& mod : modules) {
if (region.base >= mod.base && region.base < mod.base + mod.size) {
region.moduleName = mod.name;
break;
}
}
regions.append(region);
}
ULONG64 next = mbi.BaseAddress + mbi.RegionSize;
if (next <= addr) break;
addr = next;
}
});
}
// Fallback for kernel-mode debugging: QueryVirtual is unavailable,
// so use loaded modules as scannable regions. Each module image
// becomes one region — the scanner reads through module code/data.
if (regions.isEmpty() && !modules.isEmpty()) {
for (const auto& mod : modules) {
if (mod.size == 0) continue;
rcx::MemoryRegion region;
region.base = mod.base;
region.size = mod.size;
region.readable = true;
region.writable = false;
region.executable = true;
region.moduleName = mod.name;
regions.append(region);
}
}
#endif
return regions;
}
// ──────────────────────────────────────────────────────────────────────────
// WinDbgMemoryPlugin implementation
// ──────────────────────────────────────────────────────────────────────────
@@ -407,7 +598,7 @@ std::unique_ptr<rcx::Provider> WinDbgMemoryPlugin::createProvider(const QString&
*errorMsg = QString("Failed to connect to debug server.\n\n"
"Target: %1\n\n"
"Make sure WinDbg is running with a matching .server command\n"
"(e.g. .server tcp:port=5055) and the port/pipe is reachable.")
"(e.g. .server tcp:port=5056) and the port/pipe is reachable.")
.arg(target);
else if (target.startsWith("pid:", Qt::CaseInsensitive))
*errorMsg = QString("Failed to attach to process.\n\n"
@@ -436,7 +627,7 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
{
QDialog dlg(parent);
dlg.setWindowTitle("WinDbg Settings");
dlg.resize(460, 260);
dlg.resize(480, 360);
QPalette dlgPal = qApp->palette();
dlg.setPalette(dlgPal);
@@ -444,15 +635,27 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
auto* layout = new QVBoxLayout(&dlg);
QColor editBg = dlgPal.window().color().darker(115);
QString editSS = QStringLiteral(
"QLineEdit { background: %1; color: %2; border: 1px solid %3;"
" border-radius: 3px; padding: 4px 6px; }")
.arg(editBg.name(),
dlgPal.color(QPalette::Text).name(),
dlgPal.color(QPalette::Mid).name());
layout->addWidget(new QLabel(
"Connect to a running WinDbg debug server.\n"
"In WinDbg, run: .server tcp:port=5055"));
"In WinDbg, run: .server tcp:port=5056\n\n"
"Non-invasive debug and dump files only.\n"
"Execution control (bp, g, t, p) is not supported.\n"
"WinDbg Classic is recommended."));
layout->addSpacing(8);
layout->addWidget(new QLabel("Connection string:"));
auto* connEdit = new QLineEdit;
connEdit->setPlaceholderText("tcp:Port=5055,Server=localhost");
connEdit->setText("tcp:Port=5055,Server=localhost");
connEdit->setPlaceholderText("tcp:Port=5056,Server=127.0.0.1");
connEdit->setText("tcp:Port=5056,Server=127.0.0.1");
connEdit->setStyleSheet(editSS);
layout->addWidget(connEdit);
layout->addSpacing(4);
@@ -476,8 +679,72 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
layout->addLayout(row);
};
addExample(".server tcp:port=5055");
addExample(".server tcp:port=5056");
addExample(".server npipe:pipe=reclass");
// ── Debugger Tools status ──
layout->addSpacing(8);
#ifdef _WIN32
bool found = dbgToolsFound();
auto* toolsRow = new QHBoxLayout;
auto* toolsLabel = new QLabel;
if (found) {
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
QPalette tp = dlgPal;
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
toolsLabel->setPalette(tp);
} else {
toolsLabel->setText("Debugging Tools: not found");
QPalette tp = dlgPal;
tp.setColor(QPalette::WindowText, QColor(220, 120, 80));
toolsLabel->setPalette(tp);
}
toolsLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
toolsRow->addWidget(toolsLabel, 1);
auto* browseBtn = new QPushButton("Browse...");
browseBtn->setFixedWidth(70);
browseBtn->setToolTip("Locate Debugging Tools for Windows directory (contains dbgeng.dll)");
QObject::connect(browseBtn, &QPushButton::clicked, [&dlg, toolsLabel, &dlgPal]() {
QString dir = QFileDialog::getExistingDirectory(&dlg,
"Locate Debugging Tools for Windows",
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers");
if (dir.isEmpty()) return;
QString dllPath = dir + "/dbgeng.dll";
if (!QFileInfo::exists(dllPath)) {
QMessageBox::warning(&dlg, "Not Found",
"dbgeng.dll was not found in that directory.\n"
"Select the folder containing dbgeng.dll\n"
"(e.g. Debuggers\\x64).");
return;
}
QSettings settings;
settings.setValue(kSettingsKey, dir);
// Force reload on next use
s_hDbgEng = nullptr;
s_loadedDir.clear();
if (dbgToolsFound()) {
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
QPalette tp = dlgPal;
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
toolsLabel->setPalette(tp);
}
});
toolsRow->addWidget(browseBtn);
layout->addLayout(toolsRow);
if (!found) {
auto* note = new QLabel(
"The system dbgeng.dll does not support remote connections.\n"
"Install Debugging Tools for Windows or use Browse to locate them.");
QPalette np = dlgPal;
np.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
note->setPalette(np);
note->setWordWrap(true);
layout->addWidget(note);
}
#endif
layout->addStretch();
auto* btnLayout = new QHBoxLayout;

View File

@@ -9,6 +9,7 @@
// Forward declarations for DbgEng COM interfaces
struct IDebugClient;
struct IDebugDataSpaces;
struct IDebugDataSpaces2;
struct IDebugControl;
struct IDebugSymbols;
@@ -59,10 +60,11 @@ public:
QString name() const override { return m_name; }
QString kind() const override { return QStringLiteral("WinDbg"); }
QString getSymbol(uint64_t addr) const override;
QVector<rcx::MemoryRegion> enumerateRegions() const override;
bool isLive() const override { return m_isLive; }
uint64_t base() const override { return m_base; }
void setBase(uint64_t b) override { m_base = b; }
int pointerSize() const override { return m_pointerSize; }
private:
void initInterfaces(); // get IDebugDataSpaces/Control/Symbols from client
@@ -74,16 +76,19 @@ private:
template<typename Fn>
void dispatchToOwner(Fn&& fn) const;
IDebugClient* m_client = nullptr;
IDebugDataSpaces* m_dataSpaces = nullptr;
IDebugControl* m_control = nullptr;
IDebugSymbols* m_symbols = nullptr;
IDebugClient* m_client = nullptr;
IDebugDataSpaces* m_dataSpaces = nullptr;
IDebugDataSpaces2* m_dataSpaces2 = nullptr;
IDebugControl* m_control = nullptr;
IDebugSymbols* m_symbols = nullptr;
QString m_name;
uint64_t m_base = 0;
bool m_isLive = false;
bool m_writable = false;
int m_pointerSize = 8;
bool m_isRemote = false; // true when connected via DebugConnect (tcp/npipe)
mutable int m_readFailCount = 0;
// Dedicated thread for DbgEng COM operations. The remote TCP/pipe
// transport is thread-affine — all calls must happen on the thread

View File

@@ -283,9 +283,10 @@ function Find-MinGWDirectory {
$toolsDir = Join-Path $qtRoot "Tools"
if (Test-Path $toolsDir) {
# Prefer GCC-based MinGW (has g++.exe); exclude llvm-mingw. Prefer 64-bit, then newest.
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
$_.Name -match 'mingw'
}
$_.Name -match '^mingw\d+_\d+$'
} | Sort-Object -Property @{ Expression = { if ($_.Name -match '_64$') { 1 } else { 0 } }; Descending = $true }, Name -Descending
foreach ($dir in $mingwToolDirs) {
$testBin = Join-Path $dir.FullName "bin\g++.exe"

168
scripts/build_macos.sh Executable file
View File

@@ -0,0 +1,168 @@
#!/usr/bin/env bash
set -euo pipefail
print_help() {
cat <<'EOF'
Reclass macOS Build Script
Usage:
./scripts/build_macos.sh [options]
Options:
--qt-dir <path> Qt installation prefix (e.g. /opt/homebrew/opt/qt)
--build-type <type> Release | Debug | RelWithDebInfo | MinSizeRel (default: Release)
--build-dir <path> Build directory (default: <repo>/build)
--generator <name> CMake generator (default: Ninja if available)
--clean Remove build directory before configuring
--rebuild Clean then build
--package Run macdeployqt and create a zip
--tests Run ctest after build
-h, --help Show this help
Notes:
- You can set QTDIR or Qt6_DIR in your environment instead of --qt-dir.
- If Qt is installed via Homebrew, the script will try to detect it.
EOF
}
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
project_root="$(cd "${script_dir}/.." && pwd)"
qt_dir=""
build_type="Release"
build_dir="${project_root}/build"
generator=""
do_clean="false"
do_package="false"
do_tests="false"
while [[ $# -gt 0 ]]; do
case "$1" in
--qt-dir)
qt_dir="${2:-}"
shift 2
;;
--build-type)
build_type="${2:-}"
shift 2
;;
--build-dir)
build_dir="${2:-}"
shift 2
;;
--generator)
generator="${2:-}"
shift 2
;;
--clean)
do_clean="true"
shift
;;
--rebuild)
do_clean="true"
shift
;;
--package)
do_package="true"
shift
;;
--tests)
do_tests="true"
shift
;;
-h|--help)
print_help
exit 0
;;
*)
echo "Unknown argument: $1" >&2
print_help
exit 1
;;
esac
done
if [[ -z "${qt_dir}" ]]; then
if [[ -n "${QTDIR:-}" ]]; then
qt_dir="${QTDIR}"
elif [[ -n "${Qt6_DIR:-}" ]]; then
qt_dir="${Qt6_DIR}"
elif command -v brew >/dev/null 2>&1; then
if brew --prefix qt >/dev/null 2>&1; then
qt_dir="$(brew --prefix qt)"
fi
fi
fi
if ! command -v cmake >/dev/null 2>&1; then
echo "ERROR: cmake not found. Install CMake and try again." >&2
exit 1
fi
if [[ -z "${generator}" ]]; then
if command -v ninja >/dev/null 2>&1; then
generator="Ninja"
fi
fi
if [[ "${do_clean}" == "true" && -d "${build_dir}" ]]; then
echo "Cleaning build directory: ${build_dir}"
rm -rf "${build_dir}"
fi
mkdir -p "${build_dir}"
cmake_args=(
-S "${project_root}"
-B "${build_dir}"
-DCMAKE_BUILD_TYPE="${build_type}"
)
if [[ -n "${generator}" ]]; then
cmake_args+=(-G "${generator}")
fi
if [[ -n "${qt_dir}" ]]; then
export PATH="${qt_dir}/bin:${PATH}"
cmake_args+=(-DCMAKE_PREFIX_PATH="${qt_dir}")
fi
echo "Configuring..."
cmake "${cmake_args[@]}"
echo "Building..."
cmake --build "${build_dir}" --config "${build_type}"
if [[ "${do_tests}" == "true" ]]; then
echo "Running tests..."
ctest --test-dir "${build_dir}" --output-on-failure -C "${build_type}"
fi
if [[ "${do_package}" == "true" ]]; then
app_path="${build_dir}/Reclass.app"
if [[ ! -d "${app_path}" ]]; then
echo "ERROR: ${app_path} not found. Build may have failed." >&2
exit 1
fi
macdeployqt_bin=""
if [[ -n "${qt_dir}" && -x "${qt_dir}/bin/macdeployqt" ]]; then
macdeployqt_bin="${qt_dir}/bin/macdeployqt"
elif command -v macdeployqt >/dev/null 2>&1; then
macdeployqt_bin="$(command -v macdeployqt)"
fi
if [[ -z "${macdeployqt_bin}" ]]; then
echo "ERROR: macdeployqt not found. Ensure Qt is installed and in PATH." >&2
exit 1
fi
echo "Running macdeployqt..."
"${macdeployqt_bin}" "${app_path}" -always-overwrite
arch="$(uname -m)"
zip_name="Reclass-macos-${arch}-qt6.zip"
echo "Creating zip: ${zip_name}"
ditto -c -k --sequesterRsrc --keepParent "${app_path}" "${build_dir}/${zip_name}"
echo "Packaged: ${build_dir}/${zip_name}"
fi

View File

@@ -318,10 +318,10 @@ $qtRoot = Split-Path (Split-Path $selectedQtDir -Parent) -Parent
$toolsDir = Join-Path $qtRoot "Tools"
if (Test-Path $toolsDir) {
# Look for MinGW tools directory
# Prefer GCC-based MinGW (has g++.exe); exclude llvm-mingw. Prefer 64-bit, then newest.
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
$_.Name -match 'mingw'
}
$_.Name -match '^mingw\d+_\d+$'
} | Sort-Object -Property @{ Expression = { if ($_.Name -match '_64$') { 1 } else { 0 } }; Descending = $true }, Name -Descending
foreach ($dir in $mingwToolDirs) {
$testBin = Join-Path $dir.FullName "bin\g++.exe"

540
src/addressparser.cpp Normal file
View File

@@ -0,0 +1,540 @@
#include "addressparser.h"
namespace rcx {
// ── Address Expression Parser ──────────────────────────────────────────
//
// Parses expressions like:
// "7FF66CCE0000" → plain hex address
// "0x100 + 0x200" → arithmetic on hex values
// "<Program.exe> + 0xDE" → module base + offset
// "[<Program.exe> + 0xDE] - AB" → dereference pointer, then subtract
// "7ff6`6cce0000" → WinDbg-style backtick separator (stripped before parsing)
// "base + e_lfanew" → C/C++ style identifier resolution
// "0xFF & 0x0F" → bitwise AND
// "1 << 4" → shift left
//
// Grammar (C operator precedence):
//
// bitwiseOr = bitwiseXor ('|' bitwiseXor)*
// bitwiseXor = bitwiseAnd ('^' bitwiseAnd)*
// bitwiseAnd = shift ('&' shift)*
// shift = expr (('<<' | '>>') expr)*
// expr = term (('+' | '-') term)*
// term = unary (('*' | '/') unary)*
// unary = '-' unary | '~' unary | atom
// atom = '[' bitwiseOr ']' -- read pointer at address (dereference)
// | '<' moduleName '>' -- resolve module base address
// | '(' bitwiseOr ')' -- grouping
// | identifier -- C/C++ name resolved via callback
// | hexLiteral -- hex number, optional 0x prefix
//
// All numeric literals are hexadecimal (base 16).
// Identifiers: [a-zA-Z_][a-zA-Z0-9_]* containing at least one non-hex char.
// Module names with extensions (e.g. "client.dll") are scanned as one token.
// Pure hex-digit words (e.g. "DEAD") are treated as hex literals.
class ExpressionParser {
public:
ExpressionParser(const QString& input, const AddressParserCallbacks* callbacks)
: m_input(input), m_callbacks(callbacks) {}
AddressParseResult parse() {
skipSpaces();
if (atEnd())
return error("empty expression");
uint64_t value = 0;
if (!parseBitwiseOr(value))
return error(m_error);
skipSpaces();
if (!atEnd())
return error(QStringLiteral("unexpected '%1'").arg(m_input[m_pos]));
return {true, value, {}, -1};
}
private:
const QString& m_input;
const AddressParserCallbacks* m_callbacks;
int m_pos = 0;
QString m_error;
int m_errorPos = 0;
// ── Helpers ──
bool atEnd() const { return m_pos >= m_input.size(); }
QChar peek() const { return atEnd() ? QChar('\0') : m_input[m_pos]; }
void advance() { m_pos++; }
void skipSpaces() {
while (!atEnd() && m_input[m_pos].isSpace())
m_pos++;
}
AddressParseResult error(const QString& msg) const {
return {false, 0, msg, m_errorPos};
}
bool fail(const QString& msg) {
m_error = msg;
m_errorPos = m_pos;
return false;
}
bool expect(QChar ch) {
skipSpaces();
if (peek() != ch)
return fail(QStringLiteral("expected '%1'").arg(ch));
advance();
return true;
}
static bool isHexDigit(QChar ch) {
return (ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'f')
|| (ch >= 'A' && ch <= 'F');
}
static bool isIdentStart(QChar ch) {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
}
static bool isIdentChar(QChar ch) {
return isIdentStart(ch) || (ch >= '0' && ch <= '9');
}
// ── Recursive descent parsing ──
// bitwiseOr = bitwiseXor ('|' bitwiseXor)*
bool parseBitwiseOr(uint64_t& result) {
if (!parseBitwiseXor(result))
return false;
for (;;) {
skipSpaces();
if (peek() != '|')
break;
advance();
uint64_t rhs = 0;
if (!parseBitwiseXor(rhs))
return false;
result |= rhs;
}
return true;
}
// bitwiseXor = bitwiseAnd ('^' bitwiseAnd)*
bool parseBitwiseXor(uint64_t& result) {
if (!parseBitwiseAnd(result))
return false;
for (;;) {
skipSpaces();
if (peek() != '^')
break;
advance();
uint64_t rhs = 0;
if (!parseBitwiseAnd(rhs))
return false;
result ^= rhs;
}
return true;
}
// bitwiseAnd = shift ('&' shift)*
bool parseBitwiseAnd(uint64_t& result) {
if (!parseShift(result))
return false;
for (;;) {
skipSpaces();
if (peek() != '&')
break;
advance();
uint64_t rhs = 0;
if (!parseShift(rhs))
return false;
result &= rhs;
}
return true;
}
// shift = expr (('<<' | '>>') expr)*
bool parseShift(uint64_t& result) {
if (!parseExpression(result))
return false;
for (;;) {
skipSpaces();
QChar c = peek();
if (c != '<' && c != '>')
break;
// Must be << or >> (not < or > alone)
if (m_pos + 1 >= m_input.size() || m_input[m_pos + 1] != c)
break;
bool isLeft = (c == '<');
advance(); advance(); // skip << or >>
uint64_t rhs = 0;
if (!parseExpression(rhs))
return false;
result = isLeft ? (result << rhs) : (result >> rhs);
}
return true;
}
// expr = term (('+' | '-') term)*
bool parseExpression(uint64_t& result) {
if (!parseTerm(result))
return false;
for (;;) {
skipSpaces();
QChar op = peek();
if (op != '+' && op != '-')
break;
advance();
uint64_t rhs = 0;
if (!parseTerm(rhs))
return false;
result = (op == '+') ? result + rhs : result - rhs;
}
return true;
}
// term = unary (('*' | '/') unary)*
bool parseTerm(uint64_t& result) {
if (!parseUnary(result))
return false;
for (;;) {
skipSpaces();
QChar op = peek();
if (op != '*' && op != '/')
break;
advance();
uint64_t rhs = 0;
if (!parseUnary(rhs))
return false;
if (op == '*') {
result *= rhs;
} else {
if (rhs == 0)
return fail("division by zero");
result /= rhs;
}
}
return true;
}
// unary = '-' unary | '~' unary | atom
bool parseUnary(uint64_t& result) {
skipSpaces();
if (peek() == '-') {
advance();
uint64_t inner = 0;
if (!parseUnary(inner))
return false;
result = static_cast<uint64_t>(-static_cast<int64_t>(inner));
return true;
}
if (peek() == '~') {
advance();
uint64_t inner = 0;
if (!parseUnary(inner))
return false;
result = ~inner;
return true;
}
return parseAtom(result);
}
// atom = '[' bitwiseOr ']' | '<' name '>' | '(' bitwiseOr ')' | identifier | hexLiteral
bool parseAtom(uint64_t& result) {
skipSpaces();
if (atEnd())
return fail("unexpected end of expression");
QChar ch = peek();
if (ch == '[') return parseDereference(result);
if (ch == '<') return parseModuleName(result);
if (ch == '(') return parseGrouping(result);
// Try identifier before hex — identifiers start with [a-zA-Z_]
if (isIdentStart(ch))
return parseIdentifierOrHex(result);
return parseHexNumber(result);
}
// Identifier or hex literal disambiguation.
// Scan [a-zA-Z_][a-zA-Z0-9_]*. If it contains any non-hex char → identifier.
// Otherwise → backtrack and parse as hex number.
// WinDbg-style "module!symbol" is scanned as a single identifier token.
// If the identifier is followed by '(', try to parse as a built-in function call.
bool parseIdentifierOrHex(uint64_t& result) {
int start = m_pos;
bool hasNonHex = false;
// Scan full token, including "module!symbol" as one token
while (!atEnd() && isIdentChar(peek())) {
if (!isHexDigit(peek()))
hasNonHex = true;
advance();
}
// Handle module.dll / module.exe / module.sys extensions
// e.g. "client.dll + 0xFF" should parse "client.dll" as one token
if (!atEnd() && peek() == '.' && m_pos > start) {
int dotPos = m_pos;
advance(); // skip '.'
int extStart = m_pos;
while (!atEnd() && isIdentChar(peek()))
advance();
if (m_pos > extStart) {
hasNonHex = true; // '.' makes it definitively an identifier
} else {
m_pos = dotPos; // backtrack — '.' at end isn't an extension
}
}
// If we hit '!' and the next char is an identifier start, extend the token
// to include the second part (WinDbg module!symbol syntax)
if (!atEnd() && peek() == '!' && m_pos > start) {
int bangPos = m_pos;
advance(); // skip '!'
if (!atEnd() && isIdentStart(peek())) {
hasNonHex = true;
while (!atEnd() && isIdentChar(peek())) {
advance();
}
} else {
m_pos = bangPos; // backtrack — '!' at end isn't module!symbol
}
}
QString token = m_input.mid(start, m_pos - start);
if (!hasNonHex) {
// Pure hex digits (e.g. "DEAD") — backtrack, parse as hex
m_pos = start;
return parseHexNumber(result);
}
// Check for function call syntax: identifier '(' args ')'
skipSpaces();
if (peek() == '(')
return parseFunctionCall(token, result);
// It's an identifier — resolve via callback
if (!m_callbacks || !m_callbacks->resolveIdentifier) {
result = 0;
return true;
}
bool ok = false;
result = m_callbacks->resolveIdentifier(token, &ok);
if (!ok)
return fail(QStringLiteral("unknown identifier '%1'").arg(token));
return true;
}
// Built-in function call: vtop(pid, va), cr3(pid), phys(addr)
bool parseFunctionCall(const QString& name, uint64_t& result) {
advance(); // skip '('
if (name == QStringLiteral("vtop")) {
// vtop(pid, virtualAddress) → physical address
uint64_t pid = 0;
if (!parseBitwiseOr(pid)) return false;
skipSpaces();
if (peek() != ',')
return fail("vtop() requires 2 arguments: vtop(pid, va)");
advance(); // skip ','
uint64_t va = 0;
if (!parseBitwiseOr(va)) return false;
if (!expect(')')) return false;
if (!m_callbacks || !m_callbacks->vtop) {
result = 0;
return true;
}
bool ok = false;
result = m_callbacks->vtop((uint32_t)pid, va, &ok);
if (!ok)
return fail(QStringLiteral("vtop(0x%1, 0x%2) failed")
.arg(pid, 0, 16).arg(va, 0, 16));
return true;
}
if (name == QStringLiteral("cr3")) {
// cr3(pid) → CR3 value
uint64_t pid = 0;
if (!parseBitwiseOr(pid)) return false;
if (!expect(')')) return false;
if (!m_callbacks || !m_callbacks->cr3) {
result = 0;
return true;
}
bool ok = false;
result = m_callbacks->cr3((uint32_t)pid, &ok);
if (!ok)
return fail(QStringLiteral("cr3(%1) failed").arg(pid));
return true;
}
if (name == QStringLiteral("phys")) {
// phys(addr) → read 8 bytes from physical address
uint64_t addr = 0;
if (!parseBitwiseOr(addr)) return false;
if (!expect(')')) return false;
if (!m_callbacks || !m_callbacks->physRead) {
result = 0;
return true;
}
bool ok = false;
result = m_callbacks->physRead(addr, &ok);
if (!ok)
return fail(QStringLiteral("phys(0x%1) failed").arg(addr, 0, 16));
return true;
}
return fail(QStringLiteral("unknown function '%1'").arg(name));
}
// '[' bitwiseOr ']' — read the pointer value at the computed address
bool parseDereference(uint64_t& result) {
advance(); // skip '['
uint64_t address = 0;
if (!parseBitwiseOr(address))
return false;
if (!expect(']'))
return false;
// Without a callback, just return 0 (syntax-check mode)
if (!m_callbacks || !m_callbacks->readPointer) {
result = 0;
return true;
}
bool ok = false;
result = m_callbacks->readPointer(address, &ok);
if (!ok)
return fail(QStringLiteral("failed to read memory at 0x%1").arg(address, 0, 16));
return true;
}
// '<' moduleName '>' — resolve a module's base address (e.g. <Program.exe>)
bool parseModuleName(uint64_t& result) {
advance(); // skip '<'
int nameStart = m_pos;
while (!atEnd() && peek() != '>')
advance();
if (atEnd())
return fail("expected '>'");
QString name = m_input.mid(nameStart, m_pos - nameStart).trimmed();
advance(); // skip '>'
if (name.isEmpty())
return fail("empty module name");
// Without a callback, just return 0 (syntax-check mode)
if (!m_callbacks || !m_callbacks->resolveModule) {
result = 0;
return true;
}
bool ok = false;
result = m_callbacks->resolveModule(name, &ok);
if (!ok)
return fail(QStringLiteral("module '%1' not found").arg(name));
return true;
}
// '(' bitwiseOr ')' — parenthesized sub-expression for grouping
bool parseGrouping(uint64_t& result) {
advance(); // skip '('
if (!parseBitwiseOr(result))
return false;
return expect(')');
}
// Hex number with optional "0x" prefix. All literals are base-16.
bool parseHexNumber(uint64_t& result) {
skipSpaces();
if (atEnd())
return fail("unexpected end of expression");
int start = m_pos;
// Skip optional 0x/0X prefix
if (m_pos + 1 < m_input.size()
&& m_input[m_pos] == '0'
&& (m_input[m_pos + 1] == 'x' || m_input[m_pos + 1] == 'X'))
m_pos += 2;
// Consume hex digits
int digitsStart = m_pos;
while (!atEnd() && isHexDigit(peek()))
advance();
if (m_pos == digitsStart) {
m_errorPos = start;
return fail("expected hex number");
}
QString digits = m_input.mid(digitsStart, m_pos - digitsStart);
bool ok = false;
result = digits.toULongLong(&ok, 16);
if (!ok) {
m_errorPos = start;
return fail("invalid hex number");
}
return true;
}
};
// ── Public API ─────────────────────────────────────────────────────────
AddressParseResult AddressParser::evaluate(const QString& formula, int ptrSize,
const AddressParserCallbacks* cb)
{
// ptrSize is used by the caller to configure the readPointer callback;
// the parser itself doesn't need it directly.
Q_UNUSED(ptrSize);
// WinDbg displays 64-bit addresses with backtick separators for readability,
// e.g. "00007ff6`1a2b3c4d". Strip them so users can paste directly.
// Also remove ' in case user uses it
QString cleaned = formula;
cleaned.remove('`');
cleaned.remove('\'');
ExpressionParser parser(cleaned, cb);
return parser.parse();
}
QString AddressParser::validate(const QString& formula)
{
QString cleaned = formula;
cleaned.remove('`');
cleaned.remove('\'');
cleaned = cleaned.trimmed();
if (cleaned.isEmpty())
return QStringLiteral("empty");
// Parse with no callbacks — modules, dereferences, identifiers succeed but return 0.
// This checks syntax only.
ExpressionParser parser(cleaned, nullptr);
auto result = parser.parse();
return result.ok ? QString() : result.error;
}
} // namespace rcx

33
src/addressparser.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <QString>
#include <cstdint>
#include <functional>
namespace rcx {
struct AddressParseResult {
bool ok;
uint64_t value;
QString error;
int errorPos;
};
struct AddressParserCallbacks {
std::function<uint64_t(const QString& name, bool* ok)> resolveModule;
std::function<uint64_t(uint64_t addr, bool* ok)> readPointer;
std::function<uint64_t(const QString& name, bool* ok)> resolveIdentifier;
// Kernel paging functions (optional — only wired when kernel provider active)
std::function<uint64_t(uint32_t pid, uint64_t va, bool* ok)> vtop;
std::function<uint64_t(uint32_t pid, bool* ok)> cr3;
std::function<uint64_t(uint64_t physAddr, bool* ok)> physRead;
};
class AddressParser {
public:
static AddressParseResult evaluate(const QString& formula, int ptrSize = 8,
const AddressParserCallbacks* cb = nullptr);
static QString validate(const QString& formula);
};
} // namespace rcx

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,10 @@ public:
return m ? QString::fromLatin1(m->typeName) : QStringLiteral("???");
}
ComposeResult compose(uint64_t viewRootId = 0) const;
ComposeResult compose(uint64_t viewRootId = 0, bool compactColumns = false,
bool treeLines = false, bool braceWrap = false,
bool typeHints = false,
SymbolLookupFn symbolLookup = {}) const;
bool save(const QString& path);
bool load(const QString& path);
void loadData(const QString& binaryPath);
@@ -70,6 +73,7 @@ struct SavedSourceEntry {
QString filePath; // for File sources
QString providerTarget; // for plugin providers (e.g. "pid:name")
uint64_t baseAddress = 0;
QString baseAddressFormula;
};
// ── Controller ──
@@ -89,17 +93,28 @@ public:
void changeNodeKind(int nodeIdx, NodeKind newKind);
void renameNode(int nodeIdx, const QString& newName);
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
void insertNodeAbove(int beforeIdx, NodeKind kind, const QString& name);
void removeNode(int nodeIdx);
void toggleCollapse(int nodeIdx);
void materializeRefChildren(int nodeIdx);
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
void setNodeValue(int nodeIdx, int subLine, const QString& text,
bool isAscii = false, uint64_t resolvedAddr = 0);
void duplicateNode(int nodeIdx);
void convertToTypedPointer(uint64_t nodeId);
void splitHexNode(uint64_t nodeId);
void toggleBitfieldBit(uint64_t nodeId, int memberIdx);
void editBitfieldValue(uint64_t nodeId, int memberIdx);
void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);
void batchRemoveNodes(const QVector<int>& nodeIndices);
void batchChangeKind(const QVector<int>& nodeIndices, NodeKind newKind);
void deleteRootStruct(uint64_t structId);
void groupIntoUnion(const QSet<uint64_t>& nodeIds);
void dissolveUnion(uint64_t unionId);
void applyCommand(const Command& cmd, bool isUndo);
void refresh();
void applyTypePopupResult(TypePopupMode mode, int nodeIdx, const TypeEntry& entry, const QString& fullText);
uint64_t findOrCreateStructByName(const QString& typeName);
// Selection
void handleNodeClick(RcxEditor* source, int line, uint64_t nodeId,
@@ -115,6 +130,12 @@ public:
RcxDocument* document() const { return m_doc; }
void setEditorFont(const QString& fontName);
void setRefreshInterval(int ms);
void setCompactColumns(bool v);
void setTreeLines(bool v);
void setBraceWrap(bool v);
void setTypeHints(bool v);
bool typeHints() const { return m_typeHints; }
void resetProvider();
// MCP bridge accessors
void setSuppressRefresh(bool v) { m_suppressRefresh = v; }
@@ -122,13 +143,29 @@ public:
const QVector<SavedSourceEntry>& savedSources() const { return m_savedSources; }
int activeSourceIndex() const { return m_activeSourceIdx; }
void switchSource(int idx) { switchToSavedSource(idx); }
void clearSources();
void selectSource(const QString& text);
void copySavedSources(const QVector<SavedSourceEntry>& sources, int activeIdx);
// Test accessor
// Value tracking toggle (per-tab, off by default)
bool trackValues() const { return m_trackValues; }
void setTrackValues(bool on);
void resetChangeTracking();
// Cross-tab type visibility: point at the project's full document list
void setProjectDocuments(QVector<RcxDocument*>* docs) { m_projectDocs = docs; }
// Test accessors
const QHash<uint64_t, ValueHistory>& valueHistory() const { return m_valueHistory; }
const ComposeResult& lastResult() const { return m_lastResult; }
int dataExtent() const { return computeDataExtent(); }
signals:
void nodeSelected(int nodeIdx);
void selectionChanged(int count);
void contextMenuAboutToShow(QMenu* menu, int line);
void requestOpenProviderTab(const QString& pluginId, const QString& target,
const QString& title);
private:
RcxDocument* m_doc;
@@ -137,6 +174,10 @@ private:
QSet<uint64_t> m_selIds;
int m_anchorLine = -1;
bool m_suppressRefresh = false;
bool m_compactColumns = false;
bool m_treeLines = false;
bool m_braceWrap = false;
bool m_typeHints = false;
uint64_t m_viewRootId = 0;
// ── Saved sources for quick-switch ──
@@ -145,6 +186,7 @@ private:
// ── Cached type selector popup (avoids ~350ms cold-start on first show) ──
QPointer<TypeSelectorPopup> m_cachedPopup;
int m_typePopupGen = 0; // generation counter for deferred content loading
// ── Auto-refresh state ──
using PageMap = QHash<uint64_t, QByteArray>;
@@ -154,17 +196,20 @@ private:
PageMap m_prevPages;
QSet<int64_t> m_changedOffsets;
QHash<uint64_t, ValueHistory> m_valueHistory;
bool m_trackValues = true;
int m_valueTrackCooldown = 0; // suppress value recording for N refresh cycles after clear
uint64_t m_refreshGen = 0;
uint64_t m_readGen = 0;
bool m_readInFlight = false;
QVector<RcxDocument*>* m_projectDocs = nullptr;
void connectEditor(RcxEditor* editor);
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
void updateCommandRow();
void switchToSavedSource(int idx);
void pushSavedSourcesToEditors();
void showTypePopup(RcxEditor* editor, TypePopupMode mode, int nodeIdx, QPoint globalPos);
void applyTypePopupResult(TypePopupMode mode, int nodeIdx, const TypeEntry& entry, const QString& fullText);
TypeSelectorPopup* ensurePopup(RcxEditor* editor);
// ── Auto-refresh methods ──

View File

@@ -11,6 +11,7 @@
#include <array>
#include <memory>
#include <variant>
#include <QDateTime>
#include "providers/provider.h"
#include "providers/buffer_provider.h"
@@ -85,8 +86,8 @@ inline constexpr KindMeta kKindMeta[] = {
{NodeKind::Vec3, "Vec3", "vec3", 12, 1, 4, KF_Vector},
{NodeKind::Vec4, "Vec4", "vec4", 16, 1, 4, KF_Vector},
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
{NodeKind::UTF8, "UTF8", "char[]", 1, 1, 1, KF_String},
{NodeKind::UTF16, "UTF16", "wchar_t[]", 2, 1, 2, KF_String},
{NodeKind::UTF8, "UTF8", "str", 1, 1, 1, KF_String},
{NodeKind::UTF16, "UTF16", "wstr", 2, 1, 2, KF_String},
{NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container},
{NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container},
};
@@ -142,15 +143,21 @@ inline constexpr bool isMatrixKind(NodeKind k) {
inline constexpr bool isFuncPtr(NodeKind k) {
return k == NodeKind::FuncPtr32 || k == NodeKind::FuncPtr64;
}
// Hex types, pointer types, function pointers, and containers are not meaningful
// primitive-pointer targets — dereferencing them produces the same output as void*.
inline constexpr bool isValidPrimitivePtrTarget(NodeKind k) {
if (isHexNode(k)) return false;
if (k == NodeKind::Pointer32 || k == NodeKind::Pointer64) return false;
if (isFuncPtr(k)) return false;
if (k == NodeKind::Struct || k == NodeKind::Array) return false;
return true;
}
inline QStringList allTypeNamesForUI(bool stripBrackets = false) {
inline QStringList allTypeNamesForUI(bool /*stripBrackets*/ = false) {
QStringList out;
out.reserve(std::size(kKindMeta));
for (const auto& m : kKindMeta) {
QString t = QString::fromLatin1(m.typeName);
if (stripBrackets) t.remove(QStringLiteral("[]"));
out << t;
}
for (const auto& m : kKindMeta)
out << QString::fromLatin1(m.typeName);
out.sort(Qt::CaseInsensitive);
out.removeDuplicates();
return out;
@@ -170,6 +177,14 @@ enum Marker : int {
M_ACCENT = 9,
};
// ── Bitfield member (name + bit position + width within a container) ──
struct BitfieldMember {
QString name;
uint8_t bitOffset = 0; // position from LSB within the container
uint8_t bitWidth = 1; // number of bits (1..64)
};
// ── Node ──
struct Node {
@@ -180,12 +195,18 @@ struct Node {
QString classKeyword; // "struct", "class", or "enum" (empty = "struct")
uint64_t parentId = 0; // 0 = root (no parent)
int offset = 0;
bool isStatic = false; // static field — excluded from struct layout
QString offsetExpr; // C/C++ expression → absolute address (static fields only)
bool isRelative = false; // Pointer: target = base + value (RVA) instead of absolute
int arrayLen = 1; // Array: element count
int strLen = 64;
bool collapsed = false;
bool collapsed = true;
uint64_t refId = 0; // Pointer32/64: id of Struct to expand at *ptr
NodeKind elementKind = NodeKind::UInt8; // Array: element type
NodeKind elementKind = NodeKind::UInt8; // Array: element type; Pointer with ptrDepth>0: target type
int ptrDepth = 0; // Pointer: 0=struct/void ptr, 1=primitive*, 2=primitive**
int viewIndex = 0; // Array: current view offset (transient)
QVector<QPair<QString, int64_t>> enumMembers; // Enum: name→value pairs
QVector<BitfieldMember> bitfieldMembers; // Bitfield: per-bit member definitions
// Note: Returns 0 for Array-of-Struct/Array. Use tree.structSpan() for accurate size.
int byteSize() const {
@@ -197,6 +218,12 @@ struct Node {
if (elemSz <= 0) return 0;
return qMin(arrayLen, INT_MAX / elemSz) * elemSz;
}
case NodeKind::Struct:
if (classKeyword == QStringLiteral("bitfield")) {
int sz = sizeForKind(elementKind);
return sz > 0 ? sz : 4;
}
return 0;
default: return sizeForKind(kind);
}
}
@@ -212,11 +239,40 @@ struct Node {
o["classKeyword"] = classKeyword;
o["parentId"] = QString::number(parentId);
o["offset"] = offset;
if (isStatic)
o["isStatic"] = true;
if (!offsetExpr.isEmpty())
o["offsetExpr"] = offsetExpr;
if (isRelative)
o["isRelative"] = true;
o["arrayLen"] = arrayLen;
o["strLen"] = strLen;
o["collapsed"] = collapsed;
o["refId"] = QString::number(refId);
o["elementKind"] = kindToString(elementKind);
if (ptrDepth > 0)
o["ptrDepth"] = ptrDepth;
if (!enumMembers.isEmpty()) {
QJsonArray arr;
for (const auto& m : enumMembers) {
QJsonObject em;
em["name"] = m.first;
em["value"] = QString::number(m.second);
arr.append(em);
}
o["enumMembers"] = arr;
}
if (!bitfieldMembers.isEmpty()) {
QJsonArray arr;
for (const auto& m : bitfieldMembers) {
QJsonObject bm;
bm["name"] = m.name;
bm["bitOffset"] = m.bitOffset;
bm["bitWidth"] = m.bitWidth;
arr.append(bm);
}
o["bitfieldMembers"] = arr;
}
return o;
}
static Node fromJson(const QJsonObject& o) {
@@ -228,11 +284,34 @@ struct Node {
n.classKeyword = o["classKeyword"].toString();
n.parentId = o["parentId"].toString("0").toULongLong();
n.offset = o["offset"].toInt(0);
n.isStatic = o["isStatic"].toBool(o["isHelper"].toBool(false));
n.offsetExpr = o["offsetExpr"].toString();
n.isRelative = o["isRelative"].toBool(false);
n.arrayLen = qBound(1, o["arrayLen"].toInt(1), 1000000);
n.strLen = qBound(1, o["strLen"].toInt(64), 1000000);
n.collapsed = o["collapsed"].toBool(false);
n.collapsed = true; // Always load collapsed; user expands as needed
n.refId = o["refId"].toString("0").toULongLong();
n.elementKind = kindFromString(o["elementKind"].toString("UInt8"));
n.ptrDepth = qBound(0, o["ptrDepth"].toInt(0), 2);
if (o.contains("enumMembers")) {
QJsonArray arr = o["enumMembers"].toArray();
for (const auto& v : arr) {
QJsonObject em = v.toObject();
n.enumMembers.emplaceBack(em["name"].toString(),
em["value"].toString("0").toLongLong());
}
}
if (o.contains("bitfieldMembers")) {
QJsonArray arr = o["bitfieldMembers"].toArray();
for (const auto& v : arr) {
QJsonObject bm = v.toObject();
BitfieldMember m;
m.name = bm["name"].toString();
m.bitOffset = (uint8_t)qBound(0, bm["bitOffset"].toInt(0), 255);
m.bitWidth = (uint8_t)qBound(1, bm["bitWidth"].toInt(1), 64);
n.bitfieldMembers.append(m);
}
}
return n;
}
@@ -254,8 +333,11 @@ struct Node {
struct NodeTree {
QVector<Node> nodes;
uint64_t baseAddress = 0x00400000;
QString baseAddressFormula; // e.g. "<ReClass.exe> + 0x100"
int pointerSize = 8; // 4 for 32-bit targets, 8 for 64-bit
uint64_t m_nextId = 1;
mutable QHash<uint64_t, int> m_idCache;
mutable QHash<uint64_t, QVector<int>> m_childCache;
int addNode(const Node& n) {
Node copy = n;
@@ -265,13 +347,15 @@ struct NodeTree {
nodes.append(copy);
if (!m_idCache.isEmpty())
m_idCache[copy.id] = idx;
if (!m_childCache.isEmpty())
m_childCache[copy.parentId].append(idx);
return idx;
}
// Reserve a unique ID atomically (for use before pushing undo commands)
uint64_t reserveId() { return m_nextId++; }
void invalidateIdCache() const { m_idCache.clear(); }
void invalidateIdCache() const { m_idCache.clear(); m_childCache.clear(); }
int indexOfId(uint64_t id) const {
if (m_idCache.isEmpty() && !nodes.isEmpty()) {
@@ -282,11 +366,11 @@ struct NodeTree {
}
QVector<int> childrenOf(uint64_t parentId) const {
QVector<int> result;
for (int i = 0; i < nodes.size(); i++) {
if (nodes[i].parentId == parentId) result.append(i);
if (m_childCache.isEmpty() && !nodes.isEmpty()) {
for (int i = 0; i < nodes.size(); i++)
m_childCache[nodes[i].parentId].append(i);
}
return result;
return m_childCache.value(parentId);
}
// Collect node + all descendants (iterative, cycle-safe)
@@ -367,10 +451,11 @@ struct NodeTree {
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
for (int ci : kids) {
const Node& c = nodes[ci];
if (c.isStatic) continue; // static fields don't affect struct size
int sz = (c.kind == NodeKind::Struct || c.kind == NodeKind::Array)
? structSpan(c.id, childMap, visited) : c.byteSize();
int end = c.offset + sz;
if (end > maxEnd) maxEnd = end;
int64_t end = (int64_t)c.offset + sz;
if (end > maxEnd) maxEnd = (int)qMin(end, (int64_t)INT_MAX);
}
// Embedded struct reference: no own children but refId points to a struct definition
@@ -387,6 +472,10 @@ struct NodeTree {
QJsonObject toJson() const {
QJsonObject o;
o["baseAddress"] = QString::number(baseAddress, 16);
if (!baseAddressFormula.isEmpty())
o["baseAddressFormula"] = baseAddressFormula;
if (pointerSize != 8)
o["pointerSize"] = pointerSize;
o["nextId"] = QString::number(m_nextId);
QJsonArray arr;
for (const auto& n : nodes) arr.append(n.toJson());
@@ -397,8 +486,11 @@ struct NodeTree {
static NodeTree fromJson(const QJsonObject& o) {
NodeTree t;
t.baseAddress = o["baseAddress"].toString("400000").toULongLong(nullptr, 16);
t.baseAddressFormula = o["baseAddressFormula"].toString();
t.pointerSize = o["pointerSize"].toInt(8);
t.m_nextId = o["nextId"].toString("1").toULongLong();
QJsonArray arr = o["nodes"].toArray();
t.nodes.reserve(arr.size());
for (const auto& v : arr) {
Node n = Node::fromJson(v.toObject());
t.nodes.append(n);
@@ -414,6 +506,7 @@ struct NodeTree {
struct ValueHistory {
static constexpr int kCapacity = 10;
std::array<QString, kCapacity> values;
std::array<qint64, kCapacity> timestamps{}; // msec since epoch
int count = 0; // total unique values recorded
int head = 0; // next write position in ring
@@ -423,10 +516,16 @@ struct ValueHistory {
if (values[last] == v) return; // no change
}
values[head] = v;
timestamps[head] = QDateTime::currentMSecsSinceEpoch();
head = (head + 1) % kCapacity;
if (count < INT_MAX) count++;
}
void clear() {
count = 0;
head = 0;
}
int uniqueCount() const { return qMin(count, kCapacity); }
// 0=static, 1=cold(2 unique), 2=warm(3-4), 3=hot(5+)
@@ -450,6 +549,16 @@ struct ValueHistory {
for (int i = 0; i < n; i++)
fn(values[(start + i) % kCapacity]);
}
// Iterate with timestamps from newest to oldest
template<typename Fn>
void forEachWithTime(Fn&& fn) const {
int n = uniqueCount();
for (int i = 0; i < n; i++) {
int idx = (head + kCapacity - 1 - i) % kCapacity;
fn(values[idx], timestamps[idx]);
}
}
};
// ── LineMeta ──
@@ -464,6 +573,30 @@ static constexpr uint64_t kCommandRowId = UINT64_MAX;
static constexpr int kCommandRowLine = 0;
static constexpr int kFirstDataLine = 1;
static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL;
static constexpr uint64_t kArrayElemBit = 0x4000000000000000ULL; // marks array element selection
static constexpr uint64_t kArrayElemShift = 42; // bits 42-61 hold element index
static constexpr uint64_t kArrayElemMask = 0x3FFFFC0000000000ULL; // 20 bits → max 1048575 elements
// Encode an array element selection ID: nodeId | kArrayElemBit | (elemIdx << 42)
inline uint64_t makeArrayElemSelId(uint64_t nodeId, int elemIdx) {
Q_ASSERT(elemIdx >= 0);
return nodeId | kArrayElemBit | ((uint64_t)(elemIdx & 0xFFFFF) << kArrayElemShift);
}
inline int arrayElemIdxFromSelId(uint64_t selId) {
return (int)((selId & kArrayElemMask) >> kArrayElemShift);
}
// Member selection encoding (enum/bitfield members) — mirrors array element pattern
static constexpr uint64_t kMemberBit = 0x2000000000000000ULL;
static constexpr uint64_t kMemberSubShift = 42;
static constexpr uint64_t kMemberSubMask = 0x3FFFFC0000000000ULL;
inline uint64_t makeMemberSelId(uint64_t nodeId, int subLine) {
return nodeId | kMemberBit | ((uint64_t)(subLine & 0xFFFFF) << kMemberSubShift);
}
inline int memberSubFromSelId(uint64_t selId) {
return (int)((selId & kMemberSubMask) >> kMemberSubShift);
}
struct LineMeta {
int nodeIdx = -1;
@@ -494,6 +627,11 @@ struct LineMeta {
int effectiveNameW = 22; // Per-line name column width used for rendering
QString pointerTargetName; // Resolved target type name for Pointer32/64 (empty = "void")
bool isArrayElement = false; // true for synthesized primitive array element lines
bool isMemberLine = false; // true for enum member / bitfield member lines
bool isStaticLine = false; // true for static field node lines
QString typeHint; // Type inference hint text (e.g. "Float×2") — only set for hex nodes when hints enabled
int typeHintStart = -1; // Character offset where hint text starts in line text (-1 = none)
QVector<NodeKind> typeHintKinds; // Suggested kinds from inference (empty = no hint)
};
inline bool isSyntheticLine(const LineMeta& lm) {
@@ -507,6 +645,7 @@ struct LayoutInfo {
int nameW = 22; // Effective name column width (default = kColName)
int offsetHexDigits = 8; // Hex digits for offset margin (4/8/12/16)
uint64_t baseAddress = 0; // Base address for relative offset computation
bool treeLines = false; // Whether tree line connectors are embedded in the text
};
// ── ComposeResult ──
@@ -528,7 +667,7 @@ namespace cmd {
struct Insert { Node node; QVector<OffsetAdj> offAdjs; };
struct Remove { uint64_t nodeId; QVector<Node> subtree;
QVector<OffsetAdj> offAdjs; };
struct ChangeBase { uint64_t oldBase, newBase; };
struct ChangeBase { uint64_t oldBase, newBase; QString oldFormula, newFormula; };
struct WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; };
struct ChangeArrayMeta { uint64_t nodeId;
NodeKind oldElementKind, newElementKind;
@@ -538,13 +677,19 @@ namespace cmd {
struct ChangeStructTypeName { uint64_t nodeId; QString oldName, newName; };
struct ChangeClassKeyword { uint64_t nodeId; QString oldKeyword, newKeyword; };
struct ChangeOffset { uint64_t nodeId; int oldOffset, newOffset; };
struct ChangeEnumMembers { uint64_t nodeId;
QVector<QPair<QString, int64_t>> oldMembers, newMembers; };
struct ChangeOffsetExpr { uint64_t nodeId; QString oldExpr, newExpr; };
struct ToggleStatic { uint64_t nodeId; bool oldVal, newVal; };
struct ToggleRelative { uint64_t nodeId; bool oldVal, newVal; };
}
using Command = std::variant<
cmd::ChangeKind, cmd::Rename, cmd::Collapse,
cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes,
cmd::ChangeArrayMeta, cmd::ChangePointerRef, cmd::ChangeStructTypeName,
cmd::ChangeClassKeyword, cmd::ChangeOffset
cmd::ChangeClassKeyword, cmd::ChangeOffset, cmd::ChangeEnumMembers,
cmd::ChangeOffsetExpr, cmd::ToggleStatic, cmd::ToggleRelative
>;
// ── Column spans (for inline editing) ──
@@ -557,7 +702,7 @@ struct ColumnSpan {
enum class EditTarget { Name, Type, Value, BaseAddress, Source, ArrayIndex, ArrayCount,
ArrayElementType, ArrayElementCount, PointerTarget,
RootClassType, RootClassName, TypeSelector };
RootClassType, RootClassName, TypeSelector, StaticExpr };
// Column layout constants (shared with format.cpp span computation)
inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line
@@ -567,19 +712,20 @@ inline constexpr int kColValue = 96;
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
inline constexpr int kSepWidth = 1;
inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uint64_t")
inline constexpr int kMinTypeW = 7; // Minimum type column width (fits "uint8_t")
inline constexpr int kMaxTypeW = 128; // Maximum type column width
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
inline constexpr int kMaxNameW = 128; // Maximum name column width
inline constexpr int kCompactTypeW = 20; // Type column cap for compact column mode
inline ColumnSpan typeSpanFor(const LineMeta& lm, int typeW = kColType) {
if (lm.lineKind != LineKind::Field || lm.isContinuation) return {};
if (lm.lineKind != LineKind::Field || lm.isContinuation || lm.isMemberLine) return {};
int ind = kFoldCol + lm.depth * 3;
return {ind, ind + typeW, true};
}
inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int nameW = kColName) {
if (lm.isContinuation || lm.lineKind != LineKind::Field) return {};
if (lm.isContinuation || lm.lineKind != LineKind::Field || lm.isMemberLine) return {};
int ind = kFoldCol + lm.depth * 3;
int start = ind + typeW + kSepWidth;
@@ -594,6 +740,7 @@ inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int name
inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW = kColType, int nameW = kColName) {
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer ||
lm.lineKind == LineKind::ArrayElementSeparator) return {};
if (lm.isMemberLine) return {};
int ind = kFoldCol + lm.depth * 3;
// Hex uses nameW for ASCII column (same as regular name column)
@@ -612,6 +759,45 @@ inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW
return {start, start + valWidth, true};
}
// Member line spans (enum "name = value", bitfield "name : N = value")
inline ColumnSpan memberNameSpanFor(const LineMeta& lm, const QString& lineText) {
if (!lm.isMemberLine) return {};
int ind = kFoldCol + lm.depth * 3;
int eq = lineText.indexOf(QLatin1String(" = "), ind);
if (eq < 0) return {};
int nameEnd = eq;
while (nameEnd > ind && lineText[nameEnd - 1] == ' ') nameEnd--;
return {ind, nameEnd, true};
}
inline ColumnSpan memberValueSpanFor(const LineMeta& lm, const QString& lineText) {
if (!lm.isMemberLine) return {};
int eq = lineText.indexOf(QLatin1String(" = "));
if (eq < 0) return {};
int valStart = eq + 3;
int valEnd = lineText.size();
while (valEnd > valStart && lineText[valEnd - 1] == ' ') valEnd--;
return {valStart, valEnd, true};
}
// Static field expression span: locates text between "return " and "→" / "(error)" / end
inline ColumnSpan staticExprSpanFor(const LineMeta& /*lm*/, const QString& lineText) {
int ret = lineText.indexOf(QLatin1String("return "));
if (ret < 0) return {};
int exprStart = ret + 7;
// End: before arrow, before "(error)", or line end
int exprEnd = lineText.size();
int arrow = lineText.indexOf(QChar(0x2192), exprStart);
if (arrow > exprStart) exprEnd = arrow;
int err = lineText.indexOf(QLatin1String("(error)"), exprStart);
if (err > exprStart && err < exprEnd) exprEnd = err;
// Also stop at " }" for collapsed format
int brace = lineText.indexOf(QLatin1String(" }"), exprStart);
if (brace > exprStart && brace < exprEnd) exprEnd = brace;
while (exprEnd > exprStart && lineText[exprEnd - 1] == ' ') exprEnd--;
return {exprStart, exprEnd, true};
}
inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW = kColType, int nameW = kColName) {
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
int ind = kFoldCol + lm.depth * 3;
@@ -633,27 +819,14 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
// Line format: "source▾ · 0x140000000"
inline ColumnSpan commandRowSrcSpan(const QString& lineText) {
int idx = lineText.indexOf(QStringLiteral(" \u00B7"));
if (idx < 0) return {};
// Source label ends at the ▾ dropdown arrow
int arrow = lineText.indexOf(QChar(0x25BE));
if (arrow < 0) return {};
int start = 0;
while (start < idx && !lineText[start].isLetterOrNumber()
while (start < arrow && !lineText[start].isLetterOrNumber()
&& lineText[start] != '<' && lineText[start] != '\'') start++;
if (start >= idx) return {};
// Exclude trailing ▾ from the editable span
int end = idx;
while (end > start && lineText[end - 1] == QChar(0x25BE)) end--;
if (end <= start) return {};
return {start, end, true};
}
inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
int tag = lineText.indexOf(QStringLiteral(" \u00B7"));
if (tag < 0) return {};
int start = tag + 3; // after " · "
int end = start;
while (end < lineText.size() && !lineText[end].isSpace()) end++;
if (end <= start) return {};
return {start, end, true};
if (start >= arrow) return {};
return {start, arrow, true};
}
// ── CommandRow root-class spans ──
@@ -672,6 +845,25 @@ inline int commandRowRootStart(const QString& lineText) {
return best;
}
inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
// Address starts at "0x" after the source dropdown arrow
int arrow = lineText.indexOf(QChar(0x25BE));
if (arrow < 0) return {};
int start = lineText.indexOf(QStringLiteral("0x"), arrow);
if (start < 0) {
// Formula mode: address is between arrow and root keyword
start = arrow + 1;
while (start < lineText.size() && lineText[start].isSpace()) start++;
}
// End at root keyword (struct/class/enum) or end of line
int rootStart = commandRowRootStart(lineText);
int end = (rootStart > start) ? rootStart : lineText.size();
// Trim trailing whitespace
while (end > start && lineText[end - 1].isSpace()) end--;
if (end <= start) return {};
return {start, end, true};
}
inline ColumnSpan commandRowRootTypeSpan(const QString& lineText) {
int start = commandRowRootStart(lineText);
if (start < 0) return {};
@@ -820,17 +1012,18 @@ namespace fmt {
QString fmtNodeLine(const Node& node, const Provider& prov,
uint64_t addr, int depth, int subLine = 0,
const QString& comment = {}, int colType = kColType, int colName = kColName,
const QString& typeOverride = {});
const QString& typeOverride = {}, bool compact = false);
QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDigits = 8);
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType = kColType, int colName = kColName);
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType = kColType, int colName = kColName, bool compact = false);
QString fmtStructFooter(const Node& node, int depth, int totalSize = -1);
QString fmtArrayHeader(const Node& node, int depth, int viewIdx, bool collapsed, int colType = kColType, int colName = kColName, const QString& elemStructName = {});
QString fmtArrayHeader(const Node& node, int depth, int viewIdx, bool collapsed, int colType = kColType, int colName = kColName, const QString& elemStructName = {}, bool compact = false);
QString structTypeName(const Node& node); // Full type string for struct headers
QString arrayTypeName(NodeKind elemKind, int count, const QString& structName = {});
QString pointerTypeName(NodeKind kind, const QString& targetName);
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
const Provider& prov, uint64_t addr,
const QString& ptrTypeName, int colType = kColType, int colName = kColName);
const QString& ptrTypeName, int colType = kColType, int colName = kColName,
bool compact = false);
QString validateBaseAddress(const QString& text);
QString indent(int depth);
QString readValue(const Node& node, const Provider& prov,
@@ -840,10 +1033,23 @@ namespace fmt {
QByteArray parseValue(NodeKind kind, const QString& text, bool* ok);
QByteArray parseAsciiValue(const QString& text, int expectedSize, bool* ok);
QString validateValue(NodeKind kind, const QString& text);
QString fmtEnumMember(const QString& name, int64_t value, int depth, int nameW);
QString fmtBitfieldMember(const QString& name, uint8_t bitWidth,
uint64_t value, int depth, int nameW);
uint64_t extractBits(const Provider& prov, uint64_t addr,
NodeKind containerKind,
uint8_t bitOffset, uint8_t bitWidth);
} // namespace fmt
// ── Compose function forward declaration ──
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0);
// Optional callback: given an absolute address, return a symbol name (e.g. "nt!PsActiveProcessHead")
// or empty string if no symbol matches. Used for PDB symbol annotations on rows.
using SymbolLookupFn = std::function<QString(uint64_t addr)>;
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0,
bool compactColumns = false, bool treeLines = false,
bool braceWrap = false, bool typeHints = false,
SymbolLookupFn symbolLookup = {});
} // namespace rcx

36
src/dock_tab_buttons.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <QWidget>
#include <QToolButton>
#include <QHBoxLayout>
#include <QIcon>
// Dock tab button widget (close button)
// Placed on the right side of each dock tab via QTabBar::setTabButton.
class DockTabButtons : public QWidget {
Q_OBJECT
public:
QToolButton* closeBtn;
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
auto* hl = new QHBoxLayout(this);
hl->setContentsMargins(0, 0, 0, 0);
hl->setSpacing(0);
closeBtn = new QToolButton(this);
closeBtn->setAutoRaise(true);
closeBtn->setCursor(Qt::PointingHandCursor);
closeBtn->setFixedSize(16, 16);
closeBtn->setToolTip("Close tab");
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
closeBtn->setIconSize(QSize(12, 12));
hl->addWidget(closeBtn);
}
void applyTheme(const QColor& hover) {
QString style = QStringLiteral(
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
"QToolButton:hover { background: %1; }").arg(hover.name());
closeBtn->setStyleSheet(style);
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,18 @@
#pragma once
#include "core.h"
#include "providerregistry.h"
#include "themes/theme.h"
#include <QWidget>
#include <QSet>
#include <QPoint>
#include <QHash>
class QLineEdit;
class QsciScintilla;
class QsciLexerCPP;
namespace rcx {
struct SavedSourceDisplay {
QString text;
bool active = false;
};
class RcxEditor : public QWidget {
Q_OBJECT
public:
@@ -27,9 +25,15 @@ public:
void restoreViewState(const ViewState& vs);
QsciScintilla* scintilla() const { return m_sci; }
QWidget* historyPopup() const { return m_historyPopup; }
QWidget* disasmPopup() const { return m_disasmPopup; }
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
const LineMeta* metaForLine(int line) const;
int currentNodeIndex() const;
void scrollToNodeId(uint64_t nodeId);
void showFindBar();
void dismissHistoryPopup();
void dismissAllPopups();
// ── Column span computation ──
static ColumnSpan typeSpan(const LineMeta& lm, int typeW = kColType);
@@ -43,6 +47,8 @@ public:
bool isEditing() const { return m_editState.active; }
bool beginInlineEdit(EditTarget target, int line = -1, int col = -1);
void cancelInlineEdit();
void setHexEditPending(bool v) { m_hexEditPending = v; }
void setStaticCompletions(const QStringList& words) { m_staticCompletions = words; }
void applySelectionOverlay(const QSet<uint64_t>& selIds);
void setCommandRowText(const QString& line);
@@ -59,6 +65,8 @@ public:
m_disasmProvider = prov; m_disasmRealProv = realProv; m_disasmTree = tree;
}
void setRelativeOffsets(bool rel) { m_relativeOffsets = rel; reformatMargins(); }
// Saved sources for quick-switch in source picker
void setSavedSources(const QVector<SavedSourceDisplay>& sources) { m_savedSourceDisplay = sources; }
@@ -68,10 +76,16 @@ signals:
void keywordConvertRequested(const QString& newKeyword);
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
void inlineEditCommitted(int nodeIdx, int subLine,
EditTarget target, const QString& text);
EditTarget target, const QString& text,
uint64_t resolvedAddr = 0);
void inlineEditCancelled();
void typeSelectorRequested();
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
void insertAboveRequested(int nodeIdx, NodeKind kind);
void relativeOffsetsChanged(bool relative);
void appendBytesRequested(uint64_t structId, int byteCount);
void trimHexRequested(uint64_t structId);
void appendEnumMembersRequested(uint64_t enumId, int count);
protected:
bool eventFilter(QObject* obj, QEvent* event) override;
@@ -93,8 +107,12 @@ private:
bool m_hoverInside = false;
uint64_t m_hoveredNodeId = 0;
int m_hoveredLine = -1;
uint64_t m_prevHoveredNodeId = 0; // for incremental marker update
int m_prevHoveredLine = -1; // for incremental marker update
QSet<uint64_t> m_currentSelIds;
QVector<int> m_hoverSpanLines; // Lines with hover span indicators
// ── nodeId → display-line index (built in applyDocument) ──
QHash<uint64_t, QVector<int>> m_nodeLineIndex;
// ── Drag selection ──
bool m_dragging = false;
bool m_dragStarted = false; // true once drag threshold exceeded
@@ -122,8 +140,10 @@ private:
NodeKind editKind = NodeKind::Int32;
int commentCol = -1; // fixed comment column (stored at edit start)
bool lastValidationOk = true; // track state to avoid redundant updates
bool hexOverwrite = false; // true for hex-byte / ASCII-preview fixed-length editing
};
InlineEditState m_editState;
QStringList m_staticCompletions; // autocomplete words for StaticExpr editing
// ── Tab cycling state ──
EditTarget m_lastTabTarget = EditTarget::Value;
@@ -137,11 +157,22 @@ private:
// ── Value history ref (owned by controller) ──
const QHash<uint64_t, ValueHistory>* m_valueHistory = nullptr;
QWidget* m_historyPopup = nullptr; // ValueHistoryPopup (file-local class in editor.cpp)
QWidget* m_disasmPopup = nullptr; // DisasmPopup (file-local class in editor.cpp)
QWidget* m_disasmPopup = nullptr; // TitleBodyPopup (file-local class in editor.cpp)
QWidget* m_structPreviewPopup = nullptr; // TitleBodyPopup (file-local class in editor.cpp)
QWidget* m_arrowTooltip = nullptr; // RcxTooltip (arrow callout)
const Provider* m_disasmProvider = nullptr; // snapshot or real — for reading tree data
const Provider* m_disasmRealProv = nullptr; // real process provider — for reading code at arbitrary addresses
const NodeTree* m_disasmTree = nullptr;
// ── Find bar ──
QWidget* m_findBarContainer = nullptr;
QLineEdit* m_findBar = nullptr;
long m_findPos = 0;
void hideFindBar();
// ── Hex inline edit ──
bool m_hexEditPending = false; // set by context menu before calling beginInlineEdit
// ── Reentrancy guards ──
bool m_applyingDocument = false;
bool m_clampingSelection = false;
@@ -154,13 +185,11 @@ private:
void setupMarkers();
void allocateMarginStyles();
void applyMarginText(const QVector<LineMeta>& meta);
void applyLineAttributes(const QVector<LineMeta>& meta);
void reformatMargins();
void applyMarkers(const QVector<LineMeta>& meta);
void applyFoldLevels(const QVector<LineMeta>& meta);
void applyHexDimming(const QVector<LineMeta>& meta);
void applyHeatmapHighlight(const QVector<LineMeta>& meta);
void applySymbolColoring(const QVector<LineMeta>& meta);
void applyHeatmapHighlight(const QVector<LineMeta>& meta, const QVector<QString>& lineTexts);
void applySymbolColoring(const QVector<LineMeta>& meta, const QVector<QString>& lineTexts);
void applyBaseAddressColoring(const QVector<LineMeta>& meta);
void applyCommandRowPills();
@@ -168,6 +197,7 @@ private:
int editEndCol() const;
bool handleNormalKey(QKeyEvent* ke);
bool handleEditKey(QKeyEvent* ke);
bool handleHexEditKey(QKeyEvent* ke);
void showTypeAutocomplete();
void showSourcePicker();
void showTypeListFiltered(const QString& filter);

File diff suppressed because one or more lines are too long

616
src/examples/MMPFN.rcx Normal file
View File

@@ -0,0 +1,616 @@
{
"baseAddress": "FFFFCA8010000000",
"nextId": "3000",
"nodes": [
{
"id": "100",
"kind": "Struct",
"name": "list_entry",
"structTypeName": "_LIST_ENTRY",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "101",
"kind": "Pointer64",
"name": "Flink",
"offset": 0,
"parentId": "100",
"refId": "100",
"collapsed": true
},
{
"id": "102",
"kind": "Pointer64",
"name": "Blink",
"offset": 8,
"parentId": "100",
"refId": "100",
"collapsed": true
},
{
"id": "200",
"kind": "Struct",
"name": "balanced_node",
"structTypeName": "_RTL_BALANCED_NODE",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "210",
"kind": "Struct",
"name": "",
"classKeyword": "union",
"offset": 0,
"parentId": "200",
"refId": "0",
"collapsed": false
},
{
"id": "211",
"kind": "Pointer64",
"name": "Left",
"offset": 0,
"parentId": "210",
"refId": "200",
"collapsed": true
},
{
"id": "212",
"kind": "Pointer64",
"name": "Right",
"offset": 8,
"parentId": "210",
"refId": "200",
"collapsed": true
},
{
"id": "220",
"kind": "Struct",
"name": "",
"classKeyword": "union",
"offset": 16,
"parentId": "200",
"refId": "0",
"collapsed": false
},
{
"id": "221",
"kind": "UInt64",
"name": "ParentValue",
"offset": 0,
"parentId": "220"
},
{
"id": "300",
"kind": "Struct",
"name": "mmpte",
"structTypeName": "_MMPTE",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "301",
"kind": "Struct",
"name": "u",
"classKeyword": "union",
"offset": 0,
"parentId": "300",
"refId": "0",
"collapsed": false
},
{
"id": "302",
"kind": "UInt64",
"name": "Long",
"offset": 0,
"parentId": "301"
},
{
"id": "303",
"kind": "Struct",
"name": "Hard",
"structTypeName": "_MMPTE_HARDWARE",
"offset": 0,
"parentId": "301",
"refId": "400",
"collapsed": true
},
{
"id": "304",
"kind": "Struct",
"name": "Proto",
"structTypeName": "_MMPTE_PROTOTYPE",
"offset": 0,
"parentId": "301",
"refId": "600",
"collapsed": true
},
{
"id": "305",
"kind": "Struct",
"name": "Soft",
"structTypeName": "_MMPTE_SOFTWARE",
"offset": 0,
"parentId": "301",
"refId": "500",
"collapsed": true
},
{
"id": "306",
"kind": "Struct",
"name": "Trans",
"structTypeName": "_MMPTE_TRANSITION",
"offset": 0,
"parentId": "301",
"refId": "700",
"collapsed": true
},
{
"id": "307",
"kind": "Struct",
"name": "Subsect",
"structTypeName": "_MMPTE_SUBSECTION",
"offset": 0,
"parentId": "301",
"refId": "800",
"collapsed": true
},
{
"id": "308",
"kind": "Struct",
"name": "TimeStamp",
"structTypeName": "_MMPTE_TIMESTAMP",
"offset": 0,
"parentId": "301",
"refId": "900",
"collapsed": true
},
{
"id": "309",
"kind": "Struct",
"name": "List",
"structTypeName": "_MMPTE_LIST",
"offset": 0,
"parentId": "301",
"refId": "1000",
"collapsed": true
},
{
"id": "400",
"kind": "Struct",
"name": "mmpte_hardware",
"structTypeName": "_MMPTE_HARDWARE",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "401",
"kind": "Hex64",
"name": "Valid:1 Dirty1:1 Owner:1 WriteThrough:1 CacheDisable:1 Accessed:1 Dirty:1 LargePage:1 Global:1 CopyOnWrite:1 Unused:1 Write:1 PageFrameNumber:40 ReservedForSoftware:4 WsleAge:4 WsleProtection:3 NoExecute:1",
"offset": 0,
"parentId": "400"
},
{
"id": "500",
"kind": "Struct",
"name": "mmpte_software",
"structTypeName": "_MMPTE_SOFTWARE",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "501",
"kind": "Hex64",
"name": "Valid:1 PageFileReserved:1 PageFileAllocated:1 ColdPage:1 SwizzleBit:1 Protection:5 Prototype:1 Transition:1 PageFileLow:4 UsedPageTableEntries:10 ShadowStack:1 OnStandbyLookaside:1 Unused:4 PageFileHigh:32",
"offset": 0,
"parentId": "500"
},
{
"id": "600",
"kind": "Struct",
"name": "mmpte_prototype",
"structTypeName": "_MMPTE_PROTOTYPE",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "601",
"kind": "Hex64",
"name": "Valid:1 DemandFillProto:1 HiberVerifyConverted:1 ReadOnly:1 SwizzleBit:1 Protection:5 Prototype:1 Combined:1 Unused1:4 ProtoAddress:48",
"offset": 0,
"parentId": "600"
},
{
"id": "700",
"kind": "Struct",
"name": "mmpte_transition",
"structTypeName": "_MMPTE_TRANSITION",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "701",
"kind": "Hex64",
"name": "Valid:1 Write:1 OnStandbyLookaside:1 IoTracker:1 SwizzleBit:1 Protection:5 Prototype:1 Transition:1 PageFrameNumber:40 Unused:12",
"offset": 0,
"parentId": "700"
},
{
"id": "800",
"kind": "Struct",
"name": "mmpte_subsection",
"structTypeName": "_MMPTE_SUBSECTION",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "801",
"kind": "Hex64",
"name": "Valid:1 Unused0:2 OnStandbyLookaside:1 SwizzleBit:1 Protection:5 Prototype:1 ColdPage:1 Unused2:3 ExecutePrivilege:1 SubsectionAddress:48",
"offset": 0,
"parentId": "800"
},
{
"id": "900",
"kind": "Struct",
"name": "mmpte_timestamp",
"structTypeName": "_MMPTE_TIMESTAMP",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "901",
"kind": "Hex64",
"name": "MustBeZero:1 Unused:3 SwizzleBit:1 Protection:5 Prototype:1 Transition:1 PageFileLow:4 Reserved:16 GlobalTimeStamp:32",
"offset": 0,
"parentId": "900"
},
{
"id": "1000",
"kind": "Struct",
"name": "mmpte_list",
"structTypeName": "_MMPTE_LIST",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1001",
"kind": "Hex64",
"name": "Valid:1 OneEntry:1 filler0:2 SwizzleBit:1 Protection:5 Prototype:1 Transition:1 filler1:13 NextEntry:39",
"offset": 0,
"parentId": "1000"
},
{
"id": "1100",
"kind": "Struct",
"name": "mipfnflink",
"structTypeName": "_MIPFNFLINK",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1101",
"kind": "Hex64",
"name": "Flink",
"offset": 0,
"parentId": "1100"
},
{
"id": "1200",
"kind": "Struct",
"name": "mipfnblink",
"structTypeName": "_MIPFNBLINK",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1201",
"kind": "Hex64",
"name": "Blink",
"offset": 0,
"parentId": "1200"
},
{
"id": "1300",
"kind": "Struct",
"name": "mmpfnentry1",
"structTypeName": "_MMPFNENTRY1",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1301",
"kind": "Hex8",
"name": "Flags",
"offset": 0,
"parentId": "1300"
},
{
"id": "1400",
"kind": "Struct",
"name": "mmpfnentry3",
"structTypeName": "_MMPFNENTRY3",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1401",
"kind": "Hex8",
"name": "Flags",
"offset": 0,
"parentId": "1400"
},
{
"id": "1500",
"kind": "Struct",
"name": "mi_pfn_flags",
"structTypeName": "_MI_PFN_FLAGS",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1501",
"kind": "Hex32",
"name": "Flags",
"offset": 0,
"parentId": "1500"
},
{
"id": "1600",
"kind": "Struct",
"name": "mi_pfn_flags4",
"structTypeName": "_MI_PFN_FLAGS4",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1601",
"kind": "Hex64",
"name": "Flags",
"offset": 0,
"parentId": "1600"
},
{
"id": "1700",
"kind": "Struct",
"name": "mi_pfn_flags5",
"structTypeName": "_MI_PFN_FLAGS5",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "1701",
"kind": "Hex32",
"name": "Flags",
"offset": 0,
"parentId": "1700"
},
{
"id": "2000",
"kind": "Struct",
"name": "mmpfn",
"structTypeName": "_MMPFN",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": false
},
{
"id": "2001",
"kind": "Struct",
"name": "",
"classKeyword": "union",
"offset": 0,
"parentId": "2000",
"refId": "0",
"collapsed": false
},
{
"id": "2010",
"kind": "Struct",
"name": "ListEntry",
"structTypeName": "_LIST_ENTRY",
"offset": 0,
"parentId": "2001",
"refId": "100",
"collapsed": true
},
{
"id": "2011",
"kind": "Struct",
"name": "TreeNode",
"structTypeName": "_RTL_BALANCED_NODE",
"offset": 0,
"parentId": "2001",
"refId": "200",
"collapsed": true
},
{
"id": "2012",
"kind": "Struct",
"name": "",
"offset": 0,
"parentId": "2001",
"refId": "0",
"collapsed": false
},
{
"id": "2013",
"kind": "Struct",
"name": "u1",
"structTypeName": "_MIPFNFLINK",
"offset": 0,
"parentId": "2012",
"refId": "1100",
"collapsed": true
},
{
"id": "2014",
"kind": "Struct",
"name": "",
"classKeyword": "union",
"offset": 8,
"parentId": "2012",
"refId": "0",
"collapsed": false
},
{
"id": "2015",
"kind": "Pointer64",
"name": "PteAddress",
"offset": 0,
"parentId": "2014",
"refId": "300",
"collapsed": true
},
{
"id": "2016",
"kind": "UInt64",
"name": "PteLong",
"offset": 0,
"parentId": "2014"
},
{
"id": "2017",
"kind": "Struct",
"name": "OriginalPte",
"structTypeName": "_MMPTE",
"offset": 16,
"parentId": "2012",
"refId": "300",
"collapsed": true
},
{
"id": "2020",
"kind": "Struct",
"name": "u2",
"structTypeName": "_MIPFNBLINK",
"offset": 24,
"parentId": "2000",
"refId": "1200",
"collapsed": true
},
{
"id": "2030",
"kind": "Struct",
"name": "u3",
"classKeyword": "union",
"offset": 32,
"parentId": "2000",
"refId": "0",
"collapsed": false
},
{
"id": "2031",
"kind": "Struct",
"name": "",
"offset": 0,
"parentId": "2030",
"refId": "0",
"collapsed": false
},
{
"id": "2032",
"kind": "UInt16",
"name": "ReferenceCount",
"offset": 0,
"parentId": "2031"
},
{
"id": "2033",
"kind": "Struct",
"name": "e1",
"structTypeName": "_MMPFNENTRY1",
"offset": 2,
"parentId": "2031",
"refId": "1300",
"collapsed": true
},
{
"id": "2034",
"kind": "Struct",
"name": "e4",
"structTypeName": "_MI_PFN_FLAGS",
"offset": 0,
"parentId": "2030",
"refId": "1500",
"collapsed": true
},
{
"id": "2040",
"kind": "Struct",
"name": "u5",
"structTypeName": "_MI_PFN_FLAGS5",
"offset": 36,
"parentId": "2000",
"refId": "1700",
"collapsed": true
},
{
"id": "2050",
"kind": "Struct",
"name": "u4",
"structTypeName": "_MI_PFN_FLAGS4",
"offset": 40,
"parentId": "2000",
"refId": "1600",
"collapsed": true
}
]
}

126
src/examples/PageTables.rcx Normal file
View File

@@ -0,0 +1,126 @@
{
"baseAddress": "0",
"nextId": "2000",
"nodes": [
{
"id": "100",
"kind": "Struct",
"name": "pte",
"structTypeName": "X64_PTE",
"classKeyword": "bitfield",
"elementKind": "UInt64",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true,
"arrayLen": 1,
"strLen": 64,
"bitfieldMembers": [
{"name": "Present", "bitOffset": 0, "bitWidth": 1},
{"name": "ReadWrite", "bitOffset": 1, "bitWidth": 1},
{"name": "UserSuper", "bitOffset": 2, "bitWidth": 1},
{"name": "WriteThrough", "bitOffset": 3, "bitWidth": 1},
{"name": "CacheDisable", "bitOffset": 4, "bitWidth": 1},
{"name": "Accessed", "bitOffset": 5, "bitWidth": 1},
{"name": "Dirty", "bitOffset": 6, "bitWidth": 1},
{"name": "PageSize", "bitOffset": 7, "bitWidth": 1},
{"name": "Global", "bitOffset": 8, "bitWidth": 1},
{"name": "AVL", "bitOffset": 9, "bitWidth": 3},
{"name": "PhysAddr", "bitOffset": 12, "bitWidth": 40},
{"name": "Available", "bitOffset": 52, "bitWidth": 7},
{"name": "ProtKey", "bitOffset": 59, "bitWidth": 4},
{"name": "NX", "bitOffset": 63, "bitWidth": 1}
]
},
{
"id": "200",
"kind": "Struct",
"name": "page_table",
"structTypeName": "X64_PAGE_TABLE",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true
},
{
"id": "201",
"kind": "Array",
"name": "entries",
"offset": 0,
"parentId": "200",
"refId": "100",
"elementKind": "Struct",
"arrayLen": 512,
"strLen": 64,
"collapsed": true
},
{
"id": "300",
"kind": "Struct",
"name": "pde_2mb",
"structTypeName": "X64_PDE_LARGE",
"classKeyword": "bitfield",
"elementKind": "UInt64",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true,
"arrayLen": 1,
"strLen": 64,
"bitfieldMembers": [
{"name": "Present", "bitOffset": 0, "bitWidth": 1},
{"name": "ReadWrite", "bitOffset": 1, "bitWidth": 1},
{"name": "UserSuper", "bitOffset": 2, "bitWidth": 1},
{"name": "WriteThrough", "bitOffset": 3, "bitWidth": 1},
{"name": "CacheDisable", "bitOffset": 4, "bitWidth": 1},
{"name": "Accessed", "bitOffset": 5, "bitWidth": 1},
{"name": "Dirty", "bitOffset": 6, "bitWidth": 1},
{"name": "PageSize", "bitOffset": 7, "bitWidth": 1},
{"name": "Global", "bitOffset": 8, "bitWidth": 1},
{"name": "AVL", "bitOffset": 9, "bitWidth": 3},
{"name": "PAT", "bitOffset": 12, "bitWidth": 1},
{"name": "Reserved", "bitOffset": 13, "bitWidth": 8},
{"name": "PhysAddr", "bitOffset": 21, "bitWidth": 31},
{"name": "Available", "bitOffset": 52, "bitWidth": 7},
{"name": "ProtKey", "bitOffset": 59, "bitWidth": 4},
{"name": "NX", "bitOffset": 63, "bitWidth": 1}
]
},
{
"id": "400",
"kind": "Struct",
"name": "pdpte_1gb",
"structTypeName": "X64_PDPTE_HUGE",
"classKeyword": "bitfield",
"elementKind": "UInt64",
"offset": 0,
"parentId": "0",
"refId": "0",
"collapsed": true,
"arrayLen": 1,
"strLen": 64,
"bitfieldMembers": [
{"name": "Present", "bitOffset": 0, "bitWidth": 1},
{"name": "ReadWrite", "bitOffset": 1, "bitWidth": 1},
{"name": "UserSuper", "bitOffset": 2, "bitWidth": 1},
{"name": "WriteThrough", "bitOffset": 3, "bitWidth": 1},
{"name": "CacheDisable", "bitOffset": 4, "bitWidth": 1},
{"name": "Accessed", "bitOffset": 5, "bitWidth": 1},
{"name": "Dirty", "bitOffset": 6, "bitWidth": 1},
{"name": "PageSize", "bitOffset": 7, "bitWidth": 1},
{"name": "Global", "bitOffset": 8, "bitWidth": 1},
{"name": "AVL", "bitOffset": 9, "bitWidth": 3},
{"name": "PAT", "bitOffset": 12, "bitWidth": 1},
{"name": "Reserved", "bitOffset": 13, "bitWidth": 17},
{"name": "PhysAddr", "bitOffset": 30, "bitWidth": 22},
{"name": "Available", "bitOffset": 52, "bitWidth": 7},
{"name": "ProtKey", "bitOffset": 59, "bitWidth": 4},
{"name": "NX", "bitOffset": 63, "bitWidth": 1}
]
}
],
"rootIds": ["200"]
}

File diff suppressed because one or more lines are too long

1
src/examples/WinSDK.rcx Normal file

File diff suppressed because one or more lines are too long

10755
src/examples/t6zm.rcx Normal file

File diff suppressed because it is too large Load Diff

42817
src/examples/windows-x86_64.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
#include "core.h"
#include "addressparser.h"
#include <cmath>
#include <cstring>
#include <limits>
@@ -22,6 +23,14 @@ static QString fit(QString s, int w) {
return s.leftJustified(w, ' ');
}
// Like fit() but overflows instead of truncating: if s exceeds w, return full string
static QString fitOverflow(const QString& s, int w) {
if (w <= 0) return {};
if (s.size() <= w)
return s.leftJustified(w, ' ');
return s;
}
// ── Type name ──
// Override seam: injectable type-name provider
@@ -64,45 +73,45 @@ QString pointerTypeName(NodeKind kind, const QString& targetName) {
// ── Value formatting ──
static QString hexVal(uint64_t v) {
return QStringLiteral("0x") + QString::number(v, 16);
return QString::asprintf("0x%llx", (unsigned long long)v);
}
static QString rawHex(uint64_t v, int digits) {
return QString::number(v, 16).rightJustified(digits, '0');
}
QString fmtInt8(int8_t v) { return hexVal((uint8_t)v); }
QString fmtInt16(int16_t v) { return hexVal((uint16_t)v); }
QString fmtInt32(int32_t v) { return hexVal((uint32_t)v); }
QString fmtInt64(int64_t v) { return hexVal((uint64_t)v); }
QString fmtInt8(int8_t v) { return QString::number(v); }
QString fmtInt16(int16_t v) { return QString::number(v); }
QString fmtInt32(int32_t v) { return QString::number(v); }
QString fmtInt64(int64_t v) { return QString::number((qlonglong)v); }
QString fmtUInt8(uint8_t v) { return hexVal(v); }
QString fmtUInt16(uint16_t v) { return hexVal(v); }
QString fmtUInt32(uint32_t v) { return hexVal(v); }
QString fmtUInt64(uint64_t v) { return hexVal(v); }
QString fmtFloat(float v) {
// Fixed 7-char body: digits + "." + decimals + "f"
// Negative values get a '-' prefix (8 chars total), positive stay 7.
if (std::isnan(v)) return QStringLiteral("NaN");
if (std::isinf(v)) return v > 0 ? QStringLiteral("inff") : QStringLiteral("-inff");
// 6 significant digits — covers full single-precision range
QString s = QString::number(v, 'g', 6);
float av = std::fabs(v);
if (av >= 100000.f)
return v < 0 ? QStringLiteral("-99999+f") : QStringLiteral("99999+f");
// If 'g' chose scientific notation, reformat as plain decimal
if (s.contains('e') || s.contains('E')) {
s = QString::number(v, 'f', 8);
if (s.contains('.')) {
int i = s.size() - 1;
while (i > 0 && s[i] == '0') i--;
if (s[i] == '.') i++; // keep at least one decimal digit
s.truncate(i + 1);
// body = digits + "." + decimals + "f", target exactly 7 chars.
// Start with max decimals, reduce if integer part is wide or rounding overflows.
for (int dec = 4; dec >= 0; dec--) {
QString body = QString::number(av, 'f', dec);
body += (dec == 0) ? QStringLiteral(".f") : QStringLiteral("f");
if (body.size() == 7) {
if (v < 0.f) body.prepend('-');
return body;
}
}
if (!s.contains('.'))
s += QStringLiteral(".f");
else
s += QLatin1Char('f');
return s;
// Rounding pushed past 99999 — use overflow cap
return v < 0 ? QStringLiteral("-99999+f") : QStringLiteral("99999+f");
}
QString fmtDouble(double v) {
QString s = QString::number(v, 'g', 6);
@@ -112,15 +121,8 @@ QString fmtDouble(double v) {
}
QString fmtBool(uint8_t v) { return v ? QStringLiteral("true") : QStringLiteral("false"); }
QString fmtPointer32(uint32_t v) {
if (v == 0) return QStringLiteral("-> NULL");
return QStringLiteral("-> ") + hexVal(v);
}
QString fmtPointer64(uint64_t v) {
if (v == 0) return QStringLiteral("-> NULL");
return QStringLiteral("-> ") + hexVal(v);
}
QString fmtPointer32(uint32_t v) { return hexVal(v); }
QString fmtPointer64(uint64_t v) { return hexVal(v); }
// ── Indentation ──
@@ -139,32 +141,43 @@ QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDig
// ── Struct type name (for width calculation) ──
QString structTypeName(const Node& node) {
// Full type string: "struct TypeName" or just "struct" if no typename
QString base = typeName(node.kind).trimmed(); // "struct"
// Named types: just the type name (e.g. "_LIST_ENTRY")
// Anonymous: just the keyword (e.g. "union", "struct")
if (!node.structTypeName.isEmpty())
return base + QStringLiteral(" ") + node.structTypeName;
return base;
return node.structTypeName;
return node.resolvedClassKeyword();
}
// ── Struct header / footer ──
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType, int colName) {
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType, int colName, bool compact) {
// Columnar format: <type> <name> { (or no brace when collapsed)
QString ind = indent(depth);
QString type = fit(structTypeName(node), colType);
QString rawType = structTypeName(node);
QString suffix = collapsed ? QString() : QStringLiteral("{");
if (node.name.isEmpty()) {
// Anonymous struct/union: "union {" with no column padding
return ind + rawType + SEP + suffix;
}
QString type = compact ? fitOverflow(rawType, colType) : fit(rawType, colType);
return ind + type + SEP + node.name + SEP + suffix;
}
QString fmtStructFooter(const Node& /*node*/, int depth, int /*totalSize*/) {
return indent(depth) + QStringLiteral("};");
QString fmtStructFooter(const Node& node, int depth, int /*totalSize*/) {
QString footer = indent(depth) + QStringLiteral("};");
if (node.resolvedClassKeyword() == QStringLiteral("enum"))
footer += QStringLiteral(" +10");
else
footer += QStringLiteral(" +10h +100h +1000h Trim");
return footer;
}
// ── Array header ──
// Columnar format: <type[count]> <name> { (or no brace when collapsed)
QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collapsed, int colType, int colName, const QString& elemStructName) {
QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collapsed, int colType, int colName, const QString& elemStructName, bool compact) {
QString ind = indent(depth);
QString type = fit(arrayTypeName(node.elementKind, node.arrayLen, elemStructName), colType);
QString rawType = arrayTypeName(node.elementKind, node.arrayLen, elemStructName);
QString type = compact ? fitOverflow(rawType, colType) : fit(rawType, colType);
QString suffix = collapsed ? QString() : QStringLiteral("{");
return ind + type + SEP + node.name + SEP + suffix;
}
@@ -173,10 +186,16 @@ QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collap
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
const Provider& prov, uint64_t addr,
const QString& ptrTypeName, int colType, int colName) {
const QString& ptrTypeName, int colType, int colName,
bool compact) {
QString ind = indent(depth);
QString type = fit(ptrTypeName, colType);
bool overflow = compact && ptrTypeName.size() > colType;
QString type = compact ? fitOverflow(ptrTypeName, colType) : fit(ptrTypeName, colType);
if (collapsed) {
if (overflow) {
// Overflow: no column padding
return ind + type + SEP + node.name + SEP + readValue(node, prov, addr, 0);
}
// Collapsed: show pointer value instead of brace (name padded for value alignment)
QString name = fit(node.name, colName);
QString val = fit(readValue(node, prov, addr, 0), COL_VALUE);
@@ -214,15 +233,18 @@ static QString bytesToAscii(const QByteArray& b, int slot) {
return out;
}
static const char kHexDigits[] = "0123456789ABCDEF";
static QString bytesToHex(const QByteArray& b, int slot) {
QString out;
out.reserve(slot * 3);
QChar buf[64]; // max slot=8 → 8*3-1=23 chars; 64 is plenty
int pos = 0;
for (int i = 0; i < slot; ++i) {
uint8_t c = (i < b.size()) ? (uint8_t)b[i] : 0;
out += QString::asprintf("%02X", (unsigned)c);
if (i + 1 < slot) out += ' ';
buf[pos++] = QLatin1Char(kHexDigits[c >> 4]);
buf[pos++] = QLatin1Char(kHexDigits[c & 0xF]);
if (i + 1 < slot) buf[pos++] = QLatin1Char(' ');
}
return out;
return QString(buf, pos);
}
static QString fmtAsciiAndBytes(const Provider& prov, uint64_t addr,
@@ -267,6 +289,30 @@ static QString readValueImpl(const Node& node, const Provider& prov,
}
case NodeKind::Pointer64: {
uint64_t val = prov.readU64(addr);
// Primitive pointer: dereference and show target value
// (hex/ptr/fnptr targets fall through to plain void* display)
if (node.ptrDepth > 0 && isValidPrimitivePtrTarget(node.elementKind) && val != 0) {
uint64_t target = val;
for (int d = 1; d < node.ptrDepth && target != 0; d++)
target = prov.isReadable(target, 8) ? prov.readU64(target) : 0;
if (target != 0 && prov.isReadable(target, sizeForKind(node.elementKind))) {
// Create a temporary node of the target kind to format the value
Node tmp;
tmp.kind = node.elementKind;
tmp.strLen = node.strLen;
QString derefVal = readValueImpl(tmp, prov, target, 0, mode);
if (display) {
QString arrow = QStringLiteral("-> ");
QString sym = prov.getSymbol(val);
if (!sym.isEmpty())
return arrow + derefVal + QStringLiteral(" // ") + sym;
return arrow + derefVal;
}
return derefVal;
}
if (!display) return rawHex(val, 16);
return fmtPointer64(val);
}
if (!display) return rawHex(val, 16);
QString s = fmtPointer64(val);
QString sym = prov.getSymbol(val);
@@ -295,7 +341,7 @@ static QString readValueImpl(const Node& node, const Provider& prov,
int count = sizeForKind(node.kind) / 4;
QStringList parts;
for (int i = 0; i < count; i++)
parts << fmtFloat(prov.readF32(addr + i * 4)).trimmed();
parts << fmtFloat(prov.readF32(addr + i * 4));
return parts.join(QStringLiteral(", "));
}
case NodeKind::Mat4x4: {
@@ -304,7 +350,7 @@ static QString readValueImpl(const Node& node, const Provider& prov,
QString line = QStringLiteral("row%1 [").arg(subLine);
for (int c = 0; c < 4; c++) {
if (c > 0) line += QStringLiteral(", ");
line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4)).trimmed();
line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4));
}
line += QStringLiteral("]");
return line;
@@ -341,12 +387,22 @@ QString readValue(const Node& node, const Provider& prov,
QString fmtNodeLine(const Node& node, const Provider& prov,
uint64_t addr, int depth, int subLine,
const QString& comment, int colType, int colName,
const QString& typeOverride) {
const QString& typeOverride, bool compact) {
QString ind = indent(depth);
QString type = typeOverride.isEmpty() ? typeName(node.kind, colType) : fit(typeOverride, colType);
QString name = fit(node.name, colName);
// Compute raw type string for overflow detection
QString rawType = typeOverride.isEmpty() ? typeNameRaw(node.kind) : typeOverride;
bool overflow = compact && rawType.size() > colType;
QString type = overflow ? fitOverflow(rawType, colType)
: (typeOverride.isEmpty() ? typeName(node.kind, colType)
: fit(typeOverride, colType));
QString name = overflow ? node.name : fit(node.name, colName);
// Effective column width for this line (accounts for overflow, clamped to hard max)
int effectiveColType = overflow ? rawType.size() : colType;
// Blank prefix for continuation lines (same width as type+sep+name+sep)
const int prefixW = colType + colName + 2 * kSepWidth;
const int prefixW = effectiveColType + (overflow ? name.size() : colName) + 2 * kSepWidth;
// Comment suffix (only present when a comment is provided; no trailing padding)
QString cmtSuffix = comment.isEmpty() ? QString()
@@ -369,7 +425,8 @@ QString fmtNodeLine(const Node& node, const Provider& prov,
return ind + type + SEP + ascii + SEP + hex + cmtSuffix;
}
QString val = fit(readValue(node, prov, addr, subLine), COL_VALUE);
QString val = overflow ? readValue(node, prov, addr, subLine)
: fit(readValue(node, prov, addr, subLine), COL_VALUE);
return ind + type + SEP + name + SEP + val + cmtSuffix;
}
@@ -604,8 +661,10 @@ QString validateValue(NodeKind kind, const QString& text) {
QString digits = hasHexPrefix ? s.mid(2) : s;
if (hasHexPrefix || isHexKind) {
// Hex mode: only 0-9, a-f, A-F
// Hex mode: only 0-9, a-f, A-F (spaces allowed for multi-byte hex kinds)
bool isMultiByteHex = (kind >= NodeKind::Hex16 && kind <= NodeKind::Hex64);
for (QChar c : digits) {
if (c == ' ' && isMultiByteHex) continue;
if (!c.isDigit() && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
return QStringLiteral("invalid hex '%1'").arg(c);
}
@@ -640,43 +699,42 @@ QString validateValue(NodeKind kind, const QString& text) {
return QStringLiteral("invalid");
}
// ── Base address validation (supports simple +/- equations) ──
// ── Base address validation (delegates to AddressParser) ──
QString validateBaseAddress(const QString& text) {
QString s = text.trimmed();
if (s.isEmpty()) return QStringLiteral("empty");
//s.remove('`');
return AddressParser::validate(s);
}
int pos = 0;
bool firstTerm = true;
QString fmtEnumMember(const QString& name, int64_t value, int depth, int nameW) {
QString ind = indent(depth);
return ind + name.leftJustified(nameW) + QStringLiteral(" = ") + QString::number(value);
}
while (pos < s.size()) {
// Skip whitespace
while (pos < s.size() && s[pos].isSpace()) pos++;
if (pos >= s.size()) break;
// ── Bitfield member formatting ──
// Check for +/- operator (except first term)
if (!firstTerm) {
if (s[pos] == '+' || s[pos] == '-') pos++;
else return QStringLiteral("invalid '%1'").arg(s[pos]);
while (pos < s.size() && s[pos].isSpace()) pos++;
}
// Skip 0x prefix if present
if (pos + 1 < s.size() && s[pos] == '0' && (s[pos+1] == 'x' || s[pos+1] == 'X'))
pos += 2;
// Must have at least one hex digit
int numStart = pos;
while (pos < s.size() && (s[pos].isDigit() ||
(s[pos] >= 'a' && s[pos] <= 'f') ||
(s[pos] >= 'A' && s[pos] <= 'F'))) pos++;
if (pos == numStart) return QStringLiteral("invalid");
firstTerm = false;
uint64_t extractBits(const Provider& prov, uint64_t addr,
NodeKind containerKind,
uint8_t bitOffset, uint8_t bitWidth) {
uint64_t container = 0;
switch (containerKind) {
case NodeKind::Hex8: container = prov.readU8(addr); break;
case NodeKind::Hex16: container = prov.readU16(addr); break;
case NodeKind::Hex32: container = prov.readU32(addr); break;
default: container = prov.readU64(addr); break;
}
Q_ASSERT(bitOffset + bitWidth <= 64);
if (bitWidth >= 64) return container >> bitOffset;
return (container >> bitOffset) & ((1ULL << bitWidth) - 1);
}
return {};
QString fmtBitfieldMember(const QString& name, uint8_t bitWidth,
uint64_t value, int depth, int nameW) {
QString ind = indent(depth);
return ind + name.leftJustified(nameW)
+ QStringLiteral(" : %1 = %2").arg(bitWidth).arg(value);
}
} // namespace rcx::fmt

File diff suppressed because it is too large Load Diff

View File

@@ -6,14 +6,82 @@
namespace rcx {
// Generate C++ struct definitions for a single root struct and all
// nested/referenced types reachable from it.
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr);
// ── Code output format ──
// Generate C++ struct definitions for every root-level struct (full SDK).
enum class CodeFormat : int {
CppHeader = 0, // C/C++ struct definitions
RustStruct, // Rust #[repr(C)] struct definitions
DefineOffsets, // #define ClassName_FieldName 0xNN
CSharpStruct, // C# [StructLayout] with [FieldOffset]
PythonCtypes, // Python ctypes.Structure
_Count
};
enum class CodeScope : int {
Current = 0, // Just the selected struct
WithChildren, // Selected struct + all referenced types
FullSdk, // All root-level structs
_Count
};
const char* codeFormatName(CodeFormat fmt);
const char* codeFormatFileFilter(CodeFormat fmt);
const char* codeScopeName(CodeScope scope);
// ── Format-aware dispatch (calls the appropriate backend) ──
QString renderCode(CodeFormat fmt, const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
// Render rootStructId + all struct types reachable from it
QString renderCodeTree(CodeFormat fmt, const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderCodeAll(CodeFormat fmt, const NodeTree& tree,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
// ── Individual backends ──
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderCppTree(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderCppAll(const NodeTree& tree,
const QHash<NodeKind, QString>* typeAliases = nullptr);
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderRust(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderRustTree(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderRustAll(const NodeTree& tree,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderDefines(const NodeTree& tree, uint64_t rootStructId);
QString renderDefinesTree(const NodeTree& tree, uint64_t rootStructId);
QString renderDefinesAll(const NodeTree& tree);
QString renderCSharp(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderCSharpTree(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderCSharpAll(const NodeTree& tree,
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
QString renderPython(const NodeTree& tree, uint64_t rootStructId);
QString renderPythonTree(const NodeTree& tree, uint64_t rootStructId);
QString renderPythonAll(const NodeTree& tree);
// Null generator placeholder (returns empty string).
QString renderNull(const NodeTree& tree, uint64_t rootStructId);

BIN
src/icons/class.icns Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -115,6 +115,24 @@ bool exportReclassXml(const NodeTree& tree, const QString& filePath, QString* er
while (i < children.size()) {
const Node& child = tree.nodes[children[i]];
// Bitfield container: export as hex node (ReClassEx has no bitfield concept)
if (child.kind == NodeKind::Struct
&& child.resolvedClassKeyword() == QStringLiteral("bitfield")) {
int sz = child.byteSize();
if (sz <= 0) sz = 4;
xml.writeStartElement(QStringLiteral("Node"));
xml.writeAttribute(QStringLiteral("Name"), child.name);
NodeKind hexKind = (sz <= 1) ? NodeKind::Hex8 : (sz <= 2) ? NodeKind::Hex16
: (sz <= 4) ? NodeKind::Hex32 : NodeKind::Hex64;
xml.writeAttribute(QStringLiteral("Type"), QString::number(xmlTypeForKind(hexKind)));
xml.writeAttribute(QStringLiteral("Size"), QString::number(sz));
xml.writeAttribute(QStringLiteral("bHidden"), QStringLiteral("false"));
xml.writeAttribute(QStringLiteral("Comment"), QStringLiteral("bitfield"));
xml.writeEndElement();
i++;
continue;
}
// Collapse consecutive hex nodes into a single Custom node (Type=21)
if (isHexNode(child.kind)) {
int runStart = child.offset;

1264
src/imports/import_pdb.cpp Normal file

File diff suppressed because it is too large Load Diff

54
src/imports/import_pdb.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include "core.h"
#include <QVector>
#include <functional>
namespace rcx {
// ── PDB Symbol Extraction ──
struct PdbSymbol {
QString name;
uint32_t rva;
};
struct PdbSymbolResult {
QString moduleName; // derived from PDB filename (e.g. "ntoskrnl")
QVector<PdbSymbol> symbols;
};
// Extract public/global symbols (name → RVA) from a PDB file.
// This reads the DBI stream's public and global symbol sub-streams.
PdbSymbolResult extractPdbSymbols(const QString& pdbPath,
QString* errorMsg = nullptr);
// ── PDB Type Import ──
struct PdbTypeInfo {
uint32_t typeIndex; // TPI type index
QString name; // struct/class/union/enum name
uint64_t size; // sizeof in bytes
int childCount; // direct member count
bool isUnion; // union vs struct/class
bool isEnum = false; // enum type
};
// Phase 1: Enumerate all UDT types in the PDB (fast scan, no recursive import).
QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath,
QString* errorMsg = nullptr);
// Phase 2: Import selected types with full recursive child types.
// progressCb is called with (current, total) for each top-level type;
// return false from the callback to cancel the import.
using ProgressCb = std::function<bool(int current, int total)>;
NodeTree importPdbSelected(const QString& pdbPath,
const QVector<uint32_t>& typeIndices,
QString* errorMsg = nullptr,
ProgressCb progressCb = {});
// Legacy single-call API: import one struct by name (or all if filter empty).
NodeTree importPdb(const QString& pdbPath,
const QString& structFilter = {},
QString* errorMsg = nullptr);
} // namespace rcx

View File

@@ -0,0 +1,184 @@
#include "import_pdb_dialog.h"
#include "import_pdb.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QCheckBox>
#include <QListWidget>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QApplication>
namespace rcx {
PdbImportDialog::PdbImportDialog(QWidget* parent)
: QDialog(parent)
{
setWindowTitle("Import from PDB");
resize(520, 480);
auto* layout = new QVBoxLayout(this);
// PDB path row
auto* pathRow = new QHBoxLayout;
pathRow->addWidget(new QLabel("PDB File:"));
m_pathEdit = new QLineEdit;
m_pathEdit->setPlaceholderText("Select a PDB file...");
pathRow->addWidget(m_pathEdit);
m_browseBtn = new QPushButton("...");
m_browseBtn->setFixedWidth(32);
pathRow->addWidget(m_browseBtn);
layout->addLayout(pathRow);
// Filter row
auto* filterRow = new QHBoxLayout;
filterRow->addWidget(new QLabel("Filter:"));
m_filterEdit = new QLineEdit;
m_filterEdit->setPlaceholderText("Type name filter...");
m_filterEdit->setEnabled(false);
filterRow->addWidget(m_filterEdit);
layout->addLayout(filterRow);
// Select all checkbox
m_selectAll = new QCheckBox("Select All");
m_selectAll->setEnabled(false);
layout->addWidget(m_selectAll);
// Type list
m_typeList = new QListWidget;
m_typeList->setEnabled(false);
layout->addWidget(m_typeList);
// Count label
m_countLabel = new QLabel("No PDB loaded");
layout->addWidget(m_countLabel);
// Buttons
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
m_buttons->button(QDialogButtonBox::Ok)->setText("Import");
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
layout->addWidget(m_buttons);
connect(m_browseBtn, &QPushButton::clicked, this, &PdbImportDialog::browsePdb);
connect(m_pathEdit, &QLineEdit::returnPressed, this, &PdbImportDialog::loadPdb);
connect(m_filterEdit, &QLineEdit::textChanged, this, &PdbImportDialog::filterChanged);
connect(m_selectAll, &QCheckBox::toggled, this, &PdbImportDialog::selectAllToggled);
connect(m_typeList, &QListWidget::itemChanged, this, &PdbImportDialog::updateSelectionCount);
connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
QString PdbImportDialog::pdbPath() const {
return m_pathEdit->text();
}
QVector<uint32_t> PdbImportDialog::selectedTypeIndices() const {
QVector<uint32_t> result;
for (int i = 0; i < m_typeList->count(); i++) {
auto* item = m_typeList->item(i);
if (item->checkState() == Qt::Checked) {
uint32_t typeIndex = item->data(Qt::UserRole).toUInt();
result.append(typeIndex);
}
}
return result;
}
void PdbImportDialog::browsePdb() {
QString path = QFileDialog::getOpenFileName(this,
"Select PDB File", {},
"PDB Files (*.pdb);;All Files (*)");
if (path.isEmpty()) return;
m_pathEdit->setText(path);
loadPdb();
}
void PdbImportDialog::loadPdb() {
QString path = m_pathEdit->text();
if (path.isEmpty()) return;
m_typeList->clear();
m_allTypes.clear();
m_countLabel->setText("Loading...");
m_typeList->setEnabled(false);
m_filterEdit->setEnabled(false);
m_selectAll->setEnabled(false);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
QApplication::processEvents();
QString error;
QVector<PdbTypeInfo> types = enumeratePdbTypes(path, &error);
if (types.isEmpty()) {
m_countLabel->setText(error.isEmpty() ? "No types found" : error);
return;
}
m_allTypes.reserve(types.size());
for (const auto& t : types) {
TypeItem item;
item.typeIndex = t.typeIndex;
item.name = t.name;
item.childCount = t.childCount;
item.isUnion = t.isUnion;
m_allTypes.append(item);
}
// Sort by name
std::sort(m_allTypes.begin(), m_allTypes.end(),
[](const TypeItem& a, const TypeItem& b) { return a.name < b.name; });
m_filterEdit->setEnabled(true);
m_selectAll->setEnabled(true);
m_typeList->setEnabled(true);
populateList();
}
void PdbImportDialog::populateList() {
m_typeList->blockSignals(true);
m_typeList->clear();
QString filter = m_filterEdit->text();
bool selectAll = m_selectAll->isChecked();
for (const auto& t : m_allTypes) {
if (!filter.isEmpty() && !t.name.contains(filter, Qt::CaseInsensitive))
continue;
QString label = QStringLiteral("%1 (%2 fields)")
.arg(t.name).arg(t.childCount);
auto* item = new QListWidgetItem(label, m_typeList);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(selectAll ? Qt::Checked : Qt::Unchecked);
item->setData(Qt::UserRole, t.typeIndex);
}
m_typeList->blockSignals(false);
updateSelectionCount();
}
void PdbImportDialog::filterChanged(const QString&) {
populateList();
}
void PdbImportDialog::selectAllToggled(bool) {
populateList();
}
void PdbImportDialog::updateSelectionCount() {
int checked = 0;
int total = m_typeList->count();
for (int i = 0; i < total; i++) {
if (m_typeList->item(i)->checkState() == Qt::Checked)
checked++;
}
m_countLabel->setText(QStringLiteral("%1 of %2 types selected")
.arg(checked).arg(m_allTypes.size()));
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(checked > 0);
}
} // namespace rcx

View File

@@ -0,0 +1,53 @@
#pragma once
#include <QDialog>
#include <QVector>
#include <cstdint>
class QLineEdit;
class QCheckBox;
class QListWidget;
class QLabel;
class QDialogButtonBox;
class QPushButton;
namespace rcx {
struct PdbTypeInfo;
class PdbImportDialog : public QDialog {
Q_OBJECT
public:
explicit PdbImportDialog(QWidget* parent = nullptr);
QString pdbPath() const;
QVector<uint32_t> selectedTypeIndices() const;
private slots:
void browsePdb();
void loadPdb();
void filterChanged(const QString& text);
void selectAllToggled(bool checked);
void updateSelectionCount();
private:
QLineEdit* m_pathEdit;
QPushButton* m_browseBtn;
QLineEdit* m_filterEdit;
QCheckBox* m_selectAll;
QListWidget* m_typeList;
QLabel* m_countLabel;
QDialogButtonBox* m_buttons;
struct TypeItem {
uint32_t typeIndex;
QString name;
int childCount;
bool isUnion;
};
QVector<TypeItem> m_allTypes;
void populateList();
};
} // namespace rcx

View File

@@ -80,15 +80,19 @@ static const struct { int xmlType; NodeKind kind; } kTypeMap2013[] = {
{ 30, NodeKind::Array }, // ClassPointerArray
};
static NodeKind lookupKind(int xmlType, XmlVersion ver) {
static NodeKind lookupKind(int xmlType, XmlVersion ver, int ptrSize = 8) {
NodeKind k = NodeKind::Hex8;
if (ver == XmlVersion::V2016) {
for (const auto& e : kTypeMap2016)
if (e.xmlType == xmlType) return e.kind;
if (e.xmlType == xmlType) { k = e.kind; break; }
} else {
for (const auto& e : kTypeMap2013)
if (e.xmlType == xmlType) return e.kind;
if (e.xmlType == xmlType) { k = e.kind; break; }
}
return NodeKind::Hex8; // fallback
// Remap pointer types for 32-bit targets
if (ptrSize < 8 && k == NodeKind::Pointer64)
k = NodeKind::Pointer32;
return k;
}
// Is this XML type a pointer-like type that uses the "Pointer" attribute?
@@ -135,7 +139,7 @@ struct PendingRef {
QString className;
};
NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointerSize) {
qDebug() << "[ImportXML] Opening file:" << filePath;
QFile file(filePath);
@@ -152,6 +156,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
NodeTree tree;
tree.baseAddress = 0x00400000;
tree.pointerSize = pointerSize;
// Class name → struct node ID (for pointer resolution)
QHash<QString, uint64_t> classIds;
@@ -249,7 +254,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
continue;
}
NodeKind kind = lookupKind(xmlType, version);
NodeKind kind = lookupKind(xmlType, version, pointerSize);
// Handle ClassInstanceArray: read child <Array> element
if (isClassInstanceArrayType(xmlType, version)) {
@@ -289,7 +294,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
// Defer ref resolution if array references a class
if (!arrayClassName.isEmpty()) {
pendingRefs.append({arrId, arrayClassName});
pendingRefs.push_back(PendingRef{arrId, arrayClassName});
}
childOffset += nodeSize > 0 ? nodeSize : 0;
@@ -316,7 +321,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
n.collapsed = true; // Start collapsed to avoid recursive expansion freeze
int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id;
pendingRefs.append({nodeId, ptrClass});
pendingRefs.push_back(PendingRef{nodeId, ptrClass});
childOffset += nodeSize > 0 ? nodeSize : sizeForKind(kind);
continue;
}
@@ -330,7 +335,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
if (!n.structTypeName.isEmpty()) {
int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id;
pendingRefs.append({nodeId, n.structTypeName});
pendingRefs.push_back(PendingRef{nodeId, n.structTypeName});
} else {
tree.addNode(n);
}
@@ -371,7 +376,6 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg) {
auto it = classIds.find(ref.className);
if (it != classIds.end()) {
tree.nodes[nodeIdx].refId = it.value();
tree.invalidateIdCache();
resolved++;
} else {
qDebug() << "[ImportXML] Unresolved ref:" << ref.className << "for node" << ref.nodeId;

View File

@@ -5,7 +5,9 @@ namespace rcx {
// Import a ReClass XML file (.reclass, .MemeCls, etc.) into a NodeTree.
// Supports ReClassEx, MemeClsEx, ReClass 2011/2013/2016 XML formats.
// pointerSize: 4 for 32-bit targets, 8 for 64-bit (default).
// Returns an empty NodeTree on failure; populates errorMsg if non-null.
NodeTree importReclassXml(const QString& filePath, QString* errorMsg = nullptr);
NodeTree importReclassXml(const QString& filePath, QString* errorMsg = nullptr,
int pointerSize = 8);
} // namespace rcx

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,9 @@ namespace rcx {
// Supports two modes (auto-detected):
// 1. With comment offsets (// 0xNN) - trusts the offset values
// 2. Without comment offsets - computes offsets from type sizes
// pointerSize: 4 for 32-bit targets, 8 for 64-bit (default).
// Returns an empty NodeTree on failure; populates errorMsg if non-null.
NodeTree importFromSource(const QString& sourceCode, QString* errorMsg = nullptr);
NodeTree importFromSource(const QString& sourceCode, QString* errorMsg = nullptr,
int pointerSize = 8);
} // namespace rcx

View File

@@ -0,0 +1,193 @@
#include "pe_debug_info.h"
#include "../providers/provider.h"
#include <cstring>
namespace rcx {
// Minimal PE structures (no Windows SDK dependency)
#pragma pack(push, 1)
struct DosHeader {
uint16_t e_magic; // 'MZ'
uint8_t pad[58];
int32_t e_lfanew; // offset to PE signature
};
struct CoffHeader {
uint16_t Machine;
uint16_t NumberOfSections;
uint32_t TimeDateStamp;
uint32_t PointerToSymbolTable;
uint32_t NumberOfSymbols;
uint16_t SizeOfOptionalHeader;
uint16_t Characteristics;
};
struct DataDirectory {
uint32_t VirtualAddress;
uint32_t Size;
};
// Only the fields we need from the optional header
struct OptionalHeader32 {
uint16_t Magic; // 0x10b = PE32, 0x20b = PE32+
uint8_t pad[90];
uint32_t NumberOfRvaAndSizes;
// DataDirectory[0] = Export, [1] = Import, ... [6] = Debug
};
struct OptionalHeader64 {
uint16_t Magic; // 0x20b = PE32+
uint8_t pad[106];
uint32_t NumberOfRvaAndSizes;
};
struct DebugDirectory {
uint32_t Characteristics;
uint32_t TimeDateStamp;
uint16_t MajorVersion;
uint16_t MinorVersion;
uint32_t Type;
uint32_t SizeOfData;
uint32_t AddressOfRawData; // RVA when loaded
uint32_t PointerToRawData; // file offset (not used for memory reads)
};
struct CvInfoPdb70 {
uint32_t Signature; // 'RSDS'
uint8_t Guid[16];
uint32_t Age;
// char PdbFileName[] follows
};
#pragma pack(pop)
static constexpr uint16_t kMZ = 0x5A4D;
static constexpr uint32_t kPE = 0x00004550;
static constexpr uint16_t kPE32 = 0x10b;
static constexpr uint16_t kPE32P = 0x20b;
static constexpr uint32_t kRSDS = 0x53445352;
static constexpr uint32_t kDebugType_CodeView = 2;
static QString guidToString(const uint8_t guid[16]) {
// Windows GUID is mixed-endian: Data1(4B LE), Data2(2B LE), Data3(2B LE), Data4(8B sequential)
// MS symbol server expects native integer values for Data1/2/3, sequential for Data4
uint32_t d1; memcpy(&d1, guid, 4);
uint16_t d2; memcpy(&d2, guid + 4, 2);
uint16_t d3; memcpy(&d3, guid + 6, 2);
QString s = QStringLiteral("%1%2%3")
.arg(d1, 8, 16, QLatin1Char('0'))
.arg(d2, 4, 16, QLatin1Char('0'))
.arg(d3, 4, 16, QLatin1Char('0'));
for (int i = 8; i < 16; i++)
s += QStringLiteral("%1").arg(guid[i], 2, 16, QLatin1Char('0'));
return s.toUpper();
}
PdbDebugInfo extractPdbDebugInfo(const Provider& prov, uint64_t moduleBase) {
PdbDebugInfo result;
// Read DOS header
DosHeader dos;
if (!prov.read(moduleBase, &dos, sizeof(dos)))
return result;
if (dos.e_magic != kMZ)
return result;
uint64_t peOffset = moduleBase + dos.e_lfanew;
// Read PE signature
uint32_t peSig = 0;
if (!prov.read(peOffset, &peSig, 4))
return result;
if (peSig != kPE)
return result;
// Read COFF header
uint64_t coffOffset = peOffset + 4;
CoffHeader coff;
if (!prov.read(coffOffset, &coff, sizeof(coff)))
return result;
// Read optional header magic to determine PE32 vs PE32+
uint64_t optOffset = coffOffset + sizeof(CoffHeader);
uint16_t optMagic = 0;
if (!prov.read(optOffset, &optMagic, 2))
return result;
// Locate debug data directory (index 6)
uint32_t numRvaAndSizes = 0;
uint64_t dataDirsOffset = 0;
if (optMagic == kPE32) {
// PE32: NumberOfRvaAndSizes at offset 92, data dirs at offset 96
if (!prov.read(optOffset + 92, &numRvaAndSizes, 4))
return result;
dataDirsOffset = optOffset + 96;
} else if (optMagic == kPE32P) {
// PE32+: NumberOfRvaAndSizes at offset 108, data dirs at offset 112
if (!prov.read(optOffset + 108, &numRvaAndSizes, 4))
return result;
dataDirsOffset = optOffset + 112;
} else {
return result;
}
if (numRvaAndSizes <= 6)
return result; // no debug directory
DataDirectory debugDir;
if (!prov.read(dataDirsOffset + 6 * sizeof(DataDirectory), &debugDir, sizeof(debugDir)))
return result;
if (debugDir.VirtualAddress == 0 || debugDir.Size == 0)
return result;
// Read debug directory entries
int numEntries = debugDir.Size / sizeof(DebugDirectory);
for (int i = 0; i < numEntries; i++) {
DebugDirectory entry;
uint64_t entryAddr = moduleBase + debugDir.VirtualAddress + i * sizeof(DebugDirectory);
if (!prov.read(entryAddr, &entry, sizeof(entry)))
continue;
if (entry.Type != kDebugType_CodeView)
continue;
// Read CodeView info (RSDS)
if (entry.AddressOfRawData == 0 || entry.SizeOfData < sizeof(CvInfoPdb70) + 1)
continue;
CvInfoPdb70 cv;
uint64_t cvAddr = moduleBase + entry.AddressOfRawData;
if (!prov.read(cvAddr, &cv, sizeof(cv)))
continue;
if (cv.Signature != kRSDS)
continue;
// Read PDB filename (null-terminated string after the struct)
int nameMaxLen = entry.SizeOfData - sizeof(CvInfoPdb70);
if (nameMaxLen > 260) nameMaxLen = 260;
char nameBuf[261] = {};
if (!prov.read(cvAddr + sizeof(CvInfoPdb70), nameBuf, nameMaxLen))
continue;
nameBuf[nameMaxLen] = '\0';
result.pdbName = QString::fromLatin1(nameBuf);
// Extract just the filename if it contains a path
int lastSlash = result.pdbName.lastIndexOf('\\');
if (lastSlash >= 0)
result.pdbName = result.pdbName.mid(lastSlash + 1);
int lastFwdSlash = result.pdbName.lastIndexOf('/');
if (lastFwdSlash >= 0)
result.pdbName = result.pdbName.mid(lastFwdSlash + 1);
result.guidString = guidToString(cv.Guid);
result.age = cv.Age;
result.valid = true;
return result;
}
return result;
}
} // namespace rcx

View File

@@ -0,0 +1,20 @@
#pragma once
#include <QString>
#include <cstdint>
namespace rcx {
class Provider;
struct PdbDebugInfo {
QString pdbName; // e.g. "ntoskrnl.pdb"
QString guidString; // 32 hex chars, no dashes, uppercase
uint32_t age = 0;
bool valid = false;
};
// Extract PDB debug info (GUID, age, filename) from a PE module in memory.
// Reads DOS header → PE header → debug directory → CodeView RSDS record.
PdbDebugInfo extractPdbDebugInfo(const Provider& prov, uint64_t moduleBase);
} // namespace rcx

View File

@@ -10,8 +10,9 @@
#define RCX_PLUGIN_EXPORT __attribute__((visibility("default")))
#endif
// Forward declaration
// Forward declarations
namespace rcx { class Provider; }
class QMenu;
/**
* Plugin interface for Reclass
@@ -66,7 +67,8 @@ struct PluginProcessInfo {
QString name;
QString path;
QIcon icon;
bool is32Bit = false;
PluginProcessInfo() : pid(0) {}
PluginProcessInfo(uint32_t p, const QString& n, const QString& pth = QString(), const QIcon& i = QIcon())
: pid(p), name(n), path(pth), icon(i) {}
@@ -128,6 +130,13 @@ public:
* @return true if enumerateProcesses() should be called
*/
virtual bool providesProcessList() const { return false; }
/**
* Add plugin-specific actions to the source menu (optional).
* Called each time the source menu is shown. Only add items when relevant
* (e.g., "Unload Driver" only when the driver is loaded).
*/
virtual void populatePluginMenu(QMenu*) {}
};
// Plugin factory function signature

13
src/macos_titlebar.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <QWidget>
namespace rcx {
struct Theme;
// Apply macOS native title bar color to match the theme.
// No-op on non-macOS platforms (implementation is platform-specific).
void applyMacTitleBarTheme(QWidget* window, const Theme& theme);
} // namespace rcx

43
src/macos_titlebar.mm Normal file
View File

@@ -0,0 +1,43 @@
#include "macos_titlebar.h"
#include "themes/theme.h"
#import <Cocoa/Cocoa.h>
#include <QColor>
#include <QWidget>
namespace rcx {
static NSColor* toNSColor(const QColor& color) {
return [NSColor colorWithCalibratedRed:color.redF()
green:color.greenF()
blue:color.blueF()
alpha:color.alphaF()];
}
void applyMacTitleBarTheme(QWidget* window, const Theme& theme) {
if (!window) return;
// Ensure native window is created.
window->winId();
auto* nsView = reinterpret_cast<NSView*>(window->winId());
if (!nsView) return;
NSWindow* nsWindow = [nsView window];
if (!nsWindow) return;
// Keep native traffic lights while tinting the title bar to the theme.
// Match the title text contrast by selecting the appropriate system appearance.
const qreal luminance =
0.2126 * theme.background.redF() +
0.7152 * theme.background.greenF() +
0.0722 * theme.background.blueF();
const bool isLight = luminance >= 0.5;
[nsWindow setAppearance:[NSAppearance appearanceNamed:
(isLight ? NSAppearanceNameAqua : NSAppearanceNameDarkAqua)]];
[nsWindow setTitlebarAppearsTransparent:YES];
[nsWindow setTitleVisibility:NSWindowTitleVisible];
[nsWindow setBackgroundColor:toNSColor(theme.background)];
}
} // namespace rcx

File diff suppressed because it is too large Load Diff

View File

@@ -2,27 +2,40 @@
#include "controller.h"
#include "titlebar.h"
#include "pluginmanager.h"
#include "scannerpanel.h"
#include "startpage.h"
#include "workspace_model.h"
namespace rcx { class SymbolDownloader; }
#include <QMainWindow>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QLabel>
#include <QSplitter>
#include <QTabWidget>
#include <QDockWidget>
#include <QTreeView>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QLineEdit>
#include <QMap>
#include <QButtonGroup>
#include <QComboBox>
#include <QPushButton>
#include <QTimer>
#include <QToolButton>
#include <Qsci/qsciscintilla.h>
namespace rcx {
class McpBridge;
class ShimmerLabel;
class DockGripWidget;
class WorkspaceDelegate;
class MainWindow : public QMainWindow {
Q_OBJECT
friend class McpBridge;
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow() override;
private slots:
void newClass();
@@ -48,35 +61,62 @@ private slots:
void toggleMcp();
void setEditorFont(const QString& fontName);
void exportCpp();
void exportRust();
void exportDefines();
void exportCSharp();
void exportPython();
void exportReclassXmlAction();
void importFromSource();
void importReclassXml();
void importPdb();
void showTypeAliasesDialog();
void editTheme();
void showOptionsDialog();
void showOptionsDialog(int initialPage);
public:
// Status bar helpers — separate app / MCP channels
void setAppStatus(const QString& text);
void setAppStatus(const QString& text, const QString& dimSuffix);
void setMcpStatus(const QString& text);
void clearMcpStatus();
// Project Lifecycle API
QMdiSubWindow* project_new(const QString& classKeyword = QString());
QMdiSubWindow* project_open(const QString& path = {});
bool project_save(QMdiSubWindow* sub = nullptr, bool saveAs = false);
void project_close(QMdiSubWindow* sub = nullptr);
QDockWidget* project_new(const QString& classKeyword = QString());
QDockWidget* project_open(const QString& path = {});
bool project_save(QDockWidget* dock = nullptr, bool saveAs = false);
void project_close(QDockWidget* dock = nullptr);
private:
enum ViewMode { VM_Reclass, VM_Rendered };
QMdiArea* m_mdiArea;
QLabel* m_statusLabel;
QWidget* m_centralPlaceholder;
ShimmerLabel* m_statusLabel;
QString m_appStatus;
QString m_appStatusDim;
bool m_mcpBusy = false;
QTimer* m_mcpClearTimer = nullptr;
TitleBarWidget* m_titleBar = nullptr;
QMenuBar* m_menuBar = nullptr;
bool m_menuBarTitleCase = false;
QWidget* m_borderOverlay = nullptr;
PluginManager m_pluginManager;
McpBridge* m_mcp = nullptr;
QAction* m_mcpAction = nullptr;
QAction* m_actRelOfs = nullptr;
QMenu* m_sourceMenu = nullptr;
QMenu* m_recentFilesMenu = nullptr;
struct SplitPane {
QTabWidget* tabWidget = nullptr;
RcxEditor* editor = nullptr;
QsciScintilla* rendered = nullptr;
QLineEdit* findBar = nullptr;
QWidget* findContainer = nullptr;
QWidget* renderedContainer = nullptr;
QComboBox* fmtCombo = nullptr;
QComboBox* scopeCombo = nullptr;
QToolButton* fmtGear = nullptr;
ViewMode viewMode = VM_Reclass;
uint64_t lastRenderedRootId = 0;
};
@@ -88,20 +128,38 @@ private:
QVector<SplitPane> panes;
int activePaneIdx = 0;
};
QMap<QMdiSubWindow*, TabState> m_tabs;
QMap<QDockWidget*, TabState> m_tabs;
QVector<QDockWidget*> m_docDocks; // ordered list for tabByIndex
QDockWidget* m_activeDocDock = nullptr; // tracks active document dock
QVector<QDockWidget*> m_sentinelDocks; // permanent sentinels for always-visible tab bars
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
bool m_closingAll = false; // guards spurious project_new during batch close
struct ClosingGuard {
bool& flag;
ClosingGuard(bool& f) : flag(f) { flag = true; }
~ClosingGuard() { flag = false; }
};
void rebuildAllDocs();
void createMenus();
void applyMenuBarTitleCase(bool titleCase);
void createStatusBar();
void showPluginsDialog();
void populateSourceMenu();
void addRecentFile(const QString& path);
void updateRecentFilesMenu();
QIcon makeIcon(const QString& svgPath);
RcxController* activeController() const;
TabState* activeTab();
TabState* tabByIndex(int index);
int tabCount() const { return m_tabs.size(); }
QMdiSubWindow* createTab(RcxDocument* doc);
QDockWidget* createSentinelDock();
QDockWidget* createTab(RcxDocument* doc);
QString tabTitle(const TabState& tab) const;
void setupDockTabBars();
void updateWindowTitle();
void closeAllDocDocks();
void setViewMode(ViewMode mode);
void updateRenderedView(TabState& tab, SplitPane& pane);
@@ -111,23 +169,68 @@ private:
SplitPane createSplitPane(TabState& tab);
void applyTheme(const Theme& theme);
void applyTabWidgetStyle(QTabWidget* tw);
void styleTabCloseButtons();
void syncViewButtons(ViewMode mode);
SplitPane* findPaneByTabWidget(QTabWidget* tw);
SplitPane* findActiveSplitPane();
RcxEditor* activePaneEditor();
// Workspace dock
QDockWidget* m_workspaceDock = nullptr;
QTreeView* m_workspaceTree = nullptr;
QStandardItemModel* m_workspaceModel = nullptr;
QDockWidget* m_workspaceDock = nullptr;
QTreeView* m_workspaceTree = nullptr;
QStandardItemModel* m_workspaceModel = nullptr;
QSortFilterProxyModel* m_workspaceProxy = nullptr;
QLineEdit* m_workspaceSearch = nullptr;
WorkspaceDelegate* m_workspaceDelegate = nullptr;
QLabel* m_dockTitleLabel = nullptr;
QToolButton* m_dockCloseBtn = nullptr;
DockGripWidget* m_dockGrip = nullptr;
QSet<uint64_t> m_pinnedIds;
void createWorkspaceDock();
void rebuildWorkspaceModel();
void rebuildWorkspaceModel(); // debounced — safe to call frequently
void rebuildWorkspaceModelNow(); // immediate rebuild
QTimer* m_workspaceRebuildTimer = nullptr;
QTimer* m_workspaceSearchTimer = nullptr;
void updateBorderColor(const QColor& color);
// Scanner dock
QDockWidget* m_scannerDock = nullptr;
ScannerPanel* m_scannerPanel = nullptr;
QLabel* m_scanDockTitle = nullptr;
QToolButton* m_scanDockCloseBtn = nullptr;
DockGripWidget* m_scanDockGrip = nullptr;
void createScannerDock();
// Modules/Symbols dock
QDockWidget* m_symbolsDock = nullptr;
QTabWidget* m_symTabWidget = nullptr;
// Modules tab
QTreeView* m_modulesTree = nullptr;
QStandardItemModel* m_modulesModel = nullptr;
// Symbols tab
QTreeView* m_symbolsTree = nullptr;
QStandardItemModel* m_symbolsModel = nullptr;
QSortFilterProxyModel* m_symbolsProxy = nullptr;
QLineEdit* m_symbolsSearch = nullptr;
// Title bar
QLabel* m_symDockTitle = nullptr;
QToolButton* m_symDockCloseBtn = nullptr;
QToolButton* m_symDownloadBtn = nullptr;
DockGripWidget* m_symDockGrip = nullptr;
rcx::SymbolDownloader* m_symDownloader = nullptr;
void createSymbolsDock();
void rebuildSymbolsModel();
void rebuildModulesModel();
void downloadSymbolsForProcess();
// Start page
StartPageWidget* m_startPage = nullptr;
Q_INVOKABLE void showStartPage();
void dismissStartPage();
protected:
void changeEvent(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
bool eventFilter(QObject* obj, QEvent* event) override;
};
} // namespace rcx

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QByteArray>
#include <QTimer>
namespace rcx {
@@ -28,12 +29,31 @@ public:
void notifyDataChanged();
private:
struct ClientState {
QLocalSocket* socket = nullptr;
QByteArray readBuffer;
bool initialized = false;
};
MainWindow* m_mainWindow;
QLocalServer* m_server = nullptr;
QLocalSocket* m_client = nullptr; // single client for v1
QByteArray m_readBuffer;
bool m_initialized = false;
QVector<ClientState> m_clients;
QLocalSocket* m_currentSender = nullptr; // set during request processing
bool m_slowMode = false;
QTimer* m_notifyTimer = nullptr;
// Serial request queue. Some tool calls (scanner, tree.apply) spin nested
// event loops which would let another client's readyRead interleave and
// clobber m_currentSender. Simplest fix without refactoring those tools:
// queue incoming lines while a request is in flight, drain after.
bool m_processing = false;
struct PendingRequest { QLocalSocket* socket; QByteArray line; };
QVector<PendingRequest> m_pendingRequests;
ClientState* findClient(QLocalSocket* sock);
void removeClient(QLocalSocket* sock);
void drainPendingRequests();
// JSON-RPC plumbing
void onNewConnection();
@@ -54,18 +74,26 @@ private:
QJsonObject toolProjectState(const QJsonObject& args);
QJsonObject toolTreeApply(const QJsonObject& args);
QJsonObject toolSourceSwitch(const QJsonObject& args);
QJsonObject toolSourceModules(const QJsonObject& args);
QJsonObject toolHexRead(const QJsonObject& args);
QJsonObject toolHexWrite(const QJsonObject& args);
QJsonObject toolStatusSet(const QJsonObject& args);
QJsonObject toolUiAction(const QJsonObject& args);
QJsonObject toolTreeSearch(const QJsonObject& args);
QJsonObject toolNodeHistory(const QJsonObject& args);
QJsonObject toolScannerScan(const QJsonObject& args);
QJsonObject toolScannerScanPattern(const QJsonObject& args);
QJsonObject toolReconnect(const QJsonObject& args);
QJsonObject toolProcessInfo(const QJsonObject& args);
// Helpers
QJsonObject makeTextResult(const QString& text, bool isError = false);
QString resolvePlaceholder(const QString& ref,
const QHash<QString, uint64_t>& placeholderMap);
const QHash<QString, uint64_t>& placeholderMap,
bool* ok = nullptr);
// Smart tab resolution: tabIndex arg → activeTab → first tab → auto-create
MainWindow::TabState* resolveTab(const QJsonObject& args);
MainWindow::TabState* resolveTab(const QJsonObject& args, int* resolvedIndex = nullptr);
};
} // namespace rcx

View File

@@ -40,9 +40,21 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
m_tree->setHeaderHidden(true);
m_tree->setRootIsDecorated(true);
m_tree->setFixedWidth(200);
m_tree->setMouseTracking(true);
m_tree->setIconSize(QSize(16, 16));
{
const auto& t = ThemeManager::instance().current();
QPalette tp = m_tree->palette();
tp.setColor(QPalette::Text, t.textDim);
tp.setColor(QPalette::Highlight, t.hover);
tp.setColor(QPalette::HighlightedText, t.text);
m_tree->setPalette(tp);
}
auto* envItem = new QTreeWidgetItem(m_tree, {"Environment"});
envItem->setIcon(0, QIcon(":/vsicons/folder.svg"));
auto* generalItem = new QTreeWidgetItem(envItem, {"General"});
generalItem->setIcon(0, QIcon(":/vsicons/settings-gear.svg"));
m_tree->expandAll();
m_tree->setCurrentItem(generalItem);
leftColumn->addWidget(m_tree, 1);
@@ -102,7 +114,7 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
m_fontCombo->setObjectName("fontCombo");
visualLayout->addRow("Editor Font:", m_fontCombo);
m_titleCaseCheck = new QCheckBox("Apply title case styling to menu bar");
m_titleCaseCheck = new QCheckBox("Uppercase menu items");
m_titleCaseCheck->setChecked(current.menuBarTitleCase);
visualLayout->addRow(m_titleCaseCheck);
@@ -110,25 +122,11 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
m_showIconCheck->setChecked(current.showIcon);
visualLayout->addRow(m_showIconCheck);
m_braceWrapCheck = new QCheckBox("Opening brace on new line");
m_braceWrapCheck->setChecked(current.braceWrap);
visualLayout->addRow(m_braceWrapCheck);
generalLayout->addWidget(visualGroup);
// Safe Mode group box
auto* safeModeGroup = new QGroupBox("Preview Features");
auto* safeModeLayout = new QVBoxLayout(safeModeGroup);
safeModeLayout->setSpacing(4);
m_safeModeCheck = new QCheckBox("Safe Mode");
m_safeModeCheck->setChecked(current.safeMode);
safeModeLayout->addWidget(m_safeModeCheck);
auto* safeModeDesc = new QLabel(
"Enable to use the default OS icon for this application and "
"create the window with the name of the executable file.");
safeModeDesc->setWordWrap(true);
safeModeDesc->setContentsMargins(20, 0, 0, 0); // indent under checkbox
safeModeLayout->addWidget(safeModeDesc);
generalLayout->addWidget(safeModeGroup);
generalLayout->addStretch();
m_pages->addWidget(generalPage); // index 0
@@ -136,6 +134,7 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
// -- AI Features page --
auto* aiItem = new QTreeWidgetItem(envItem, {"AI Features"});
aiItem->setIcon(0, QIcon(":/vsicons/remote.svg"));
auto* aiPage = new QWidget;
auto* aiLayout = new QVBoxLayout(aiPage);
@@ -165,11 +164,20 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
// -- Generator page --
auto* generatorItem = new QTreeWidgetItem(envItem, {"Generator"});
generatorItem->setIcon(0, QIcon(":/vsicons/code.svg"));
auto* generatorPage = new QWidget;
auto* generatorLayout = new QVBoxLayout(generatorPage);
generatorLayout->setContentsMargins(0, 0, 0, 0);
generatorLayout->setSpacing(8);
auto* cppGroup = new QGroupBox("C++ Header");
auto* cppLayout = new QVBoxLayout(cppGroup);
m_assertCheck = new QCheckBox("Emit static_assert size checks");
m_assertCheck->setChecked(current.generatorAsserts);
cppLayout->addWidget(m_assertCheck);
generatorLayout->addWidget(cppGroup);
generatorLayout->addStretch();
m_pages->addWidget(generatorPage); // index 2
@@ -199,15 +207,26 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
}
void OptionsDialog::selectPage(int index) {
for (auto it = m_itemPageIndex.begin(); it != m_itemPageIndex.end(); ++it) {
if (it.value() == index) {
m_tree->setCurrentItem(it.key());
m_pages->setCurrentIndex(index);
break;
}
}
}
OptionsResult OptionsDialog::result() const {
OptionsResult r;
r.themeIndex = m_themeCombo->currentIndex();
r.fontName = m_fontCombo->currentText();
r.menuBarTitleCase = m_titleCaseCheck->isChecked();
r.showIcon = m_showIconCheck->isChecked();
r.safeMode = m_safeModeCheck->isChecked();
r.autoStartMcp = m_autoMcpCheck->isChecked();
r.refreshMs = m_refreshSpin->value();
r.generatorAsserts = m_assertCheck->isChecked();
r.braceWrap = m_braceWrapCheck->isChecked();
return r;
}

View File

@@ -15,9 +15,10 @@ struct OptionsResult {
QString fontName;
bool menuBarTitleCase = true;
bool showIcon = false;
bool safeMode = false;
bool autoStartMcp = false;
bool autoStartMcp = true;
int refreshMs = 660;
bool generatorAsserts = false;
bool braceWrap = false;
};
class OptionsDialog : public QDialog {
@@ -26,6 +27,7 @@ public:
explicit OptionsDialog(const OptionsResult& current, QWidget* parent = nullptr);
OptionsResult result() const;
void selectPage(int index);
private:
void filterTree(const QString& text);
@@ -38,9 +40,10 @@ private:
QComboBox* m_fontCombo = nullptr;
QCheckBox* m_titleCaseCheck = nullptr;
QCheckBox* m_showIconCheck = nullptr;
QCheckBox* m_safeModeCheck = nullptr;
QCheckBox* m_autoMcpCheck = nullptr;
QSpinBox* m_refreshSpin = nullptr;
QCheckBox* m_assertCheck = nullptr;
QCheckBox* m_braceWrapCheck = nullptr;
// searchable keywords per leaf tree item
QHash<QTreeWidgetItem*, QStringList> m_pageKeywords;

View File

@@ -41,6 +41,11 @@ void PluginManager::LoadPlugins()
for (const QFileInfo& fileInfo : files)
{
// Skip the remote-inject payload binary — it's not a plugin and
// loading it (especially on Linux) spawns a rogue thread.
if (fileInfo.baseName().startsWith("rcx_payload"))
continue;
LoadPlugin(fileInfo.absoluteFilePath());
}
@@ -83,7 +88,7 @@ bool PluginManager::LoadPlugin(const QString& path)
qDebug() << "PluginManager: Loaded plugin:" << plugin->Name().c_str() << plugin->Version().c_str() << "by" << plugin->Author().c_str();
// Store plugin entry
m_entries.append({library, plugin});
m_entries.push_back(PluginEntry{library, plugin});
m_plugins.append(plugin);
// Auto-register providers in global registry
@@ -92,7 +97,8 @@ bool PluginManager::LoadPlugin(const QString& path)
IProviderPlugin* provider = static_cast<IProviderPlugin*>(plugin);
QString name = QString::fromStdString(plugin->Name());
QString identifier = name.toLower().replace(" ", "");
ProviderRegistry::instance().registerProvider(name, identifier, provider);
QString dllFileName = QFileInfo(path).fileName();
ProviderRegistry::instance().registerProvider(name, identifier, provider, dllFileName);
}
return true;

View File

@@ -5,6 +5,10 @@
#include <QMessageBox>
#include <QFileInfo>
#include <QPixmap>
#include <QSettings>
#include <QApplication>
#include <QClipboard>
#include <QMenu>
#ifdef _WIN32
#include <windows.h>
@@ -27,22 +31,9 @@ ProcessPicker::ProcessPicker(QWidget *parent)
, m_useCustomList(false)
{
ui->setupUi(this);
// Configure table
ui->processTable->setColumnWidth(0, 80); // PID column - fixed width
ui->processTable->setColumnWidth(1, 200); // Name column - fixed width
ui->processTable->horizontalHeader()->setStretchLastSection(true); // Path column - fills remaining space
ui->processTable->setWordWrap(false); // Disable word wrap for single-line display
ui->processTable->setTextElideMode(Qt::ElideLeft); // Elide from left (show end of path)
// Connect signals
connect(ui->refreshButton, &QPushButton::clicked, this, &ProcessPicker::refreshProcessList);
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
// Initial process enumeration
initUi();
refreshProcessList();
selectPreferredProcess();
}
ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent)
@@ -51,23 +42,103 @@ ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget
, m_useCustomList(true)
{
ui->setupUi(this);
// Configure table
ui->processTable->setColumnWidth(0, 80);
ui->processTable->setColumnWidth(1, 200);
initUi();
ui->refreshButton->setVisible(false);
m_allProcesses = customProcesses;
applyFilter();
selectPreferredProcess();
}
void ProcessPicker::initUi()
{
// Table configuration
ui->processTable->setColumnWidth(0, 80); // PID column
ui->processTable->setColumnWidth(1, 200); // Name column
ui->processTable->horizontalHeader()->setStretchLastSection(true);
ui->processTable->setSortingEnabled(true);
ui->processTable->setWordWrap(false);
ui->processTable->setTextElideMode(Qt::ElideLeft);
// Connect signals (no refresh button for custom lists)
ui->refreshButton->setVisible(false);
ui->processTable->setShowGrid(false);
ui->processTable->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + 6);
// Signal connections
connect(ui->refreshButton, &QPushButton::clicked, this, &ProcessPicker::refreshProcessList);
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
// Use custom process list
m_allProcesses = customProcesses;
applyFilter();
// Derive theme colors from the global palette (set by applyGlobalTheme)
QPalette pal = qApp->palette();
QString bg = pal.color(QPalette::Base).name();
QString text = pal.color(QPalette::Text).name();
QString hover = pal.color(QPalette::Mid).name();
QString surface = pal.color(QPalette::AlternateBase).name();
QString button = pal.color(QPalette::Button).name();
QString highlight= pal.color(QPalette::Highlight).name();
QString border = pal.color(QPalette::Mid).darker(120).name();
QString mutedText= pal.color(QPalette::Disabled, QPalette::WindowText).name();
QString hoverDk = pal.color(QPalette::Mid).darker(130).name();
ui->processTable->setStyleSheet(QStringLiteral(
"QTableWidget { background: %1; color: %2; border: none; }"
"QTableWidget::item { padding: 2px 6px; border: none; }"
"QTableWidget::item:hover { background: %3; padding: 2px 6px; border: none; }"
"QTableWidget::item:selected { background: %3; color: %2; padding: 2px 6px; border: none; }")
.arg(bg, text, hover));
ui->processTable->horizontalHeader()->setStyleSheet(QStringLiteral(
"QHeaderView::section { background: %1; color: %2; border: none;"
" padding: 4px 6px; text-align: left; }")
.arg(surface, text));
ui->processTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
ui->filterEdit->setStyleSheet(QStringLiteral(
"QLineEdit { background: %1; color: %2; border: 1px solid %3; padding: 2px 4px; }"
"QLineEdit:focus { border-color: %4; }")
.arg(bg, text, border, highlight));
QString btnStyle = QStringLiteral(
"QPushButton { background: %1; color: %2; border: 1px solid %3; padding: 4px 12px; }"
"QPushButton:hover { background: %4; }"
"QPushButton:pressed { background: %5; }"
"QPushButton:disabled { color: %6; }")
.arg(button, text, border, hover, hoverDk, mutedText);
ui->refreshButton->setStyleSheet(btnStyle);
ui->attachButton->setStyleSheet(btnStyle);
ui->cancelButton->setStyleSheet(btnStyle);
// Right-click context menu
ui->processTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->processTable, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
int row = ui->processTable->rowAt(pos.y());
if (row < 0) return;
auto* pidItem = ui->processTable->item(row, 0);
auto* nameItem = ui->processTable->item(row, 1);
auto* pathItem = ui->processTable->item(row, 2);
if (!pidItem || !nameItem) return;
QString pid = QString::number(pidItem->data(Qt::EditRole).toUInt());
QString name = nameItem->data(Qt::UserRole).toString();
QString path = pathItem ? pathItem->text() : QString();
QMenu menu;
auto* copyPid = menu.addAction(QStringLiteral("Copy PID"));
auto* copyName = menu.addAction(QStringLiteral("Copy Name"));
QAction* copyPath = nullptr;
if (!path.isEmpty())
copyPath = menu.addAction(QStringLiteral("Copy Path"));
auto* chosen = menu.exec(ui->processTable->viewport()->mapToGlobal(pos));
if (chosen == copyPid)
QApplication::clipboard()->setText(pid);
else if (chosen == copyName)
QApplication::clipboard()->setText(name);
else if (copyPath && chosen == copyPath)
QApplication::clipboard()->setText(path);
});
// Auto-focus filter for immediate typing
ui->filterEdit->setFocus();
}
ProcessPicker::~ProcessPicker()
@@ -97,28 +168,31 @@ void ProcessPicker::onProcessSelected()
{
auto* item = ui->processTable->currentItem();
if (!item) return;
int row = item->row();
m_selectedPid = ui->processTable->item(row, 0)->data(Qt::EditRole).toUInt();
m_selectedName = ui->processTable->item(row, 1)->text();
// Use original name stored in UserRole (without architecture suffix)
QVariant origName = ui->processTable->item(row, 1)->data(Qt::UserRole);
m_selectedName = origName.isValid() ? origName.toString()
: ui->processTable->item(row, 1)->text();
accept();
}
void ProcessPicker::enumerateProcesses()
{
QList<ProcessInfo> processes;
#ifdef _WIN32
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
QMessageBox::warning(this, "Error", "Failed to enumerate processes.");
return;
}
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (Process32FirstW(snapshot, &pe32))
{
do
@@ -126,10 +200,7 @@ void ProcessPicker::enumerateProcesses()
ProcessInfo info;
info.pid = pe32.th32ProcessID;
info.name = QString::fromWCharArray(pe32.szExeFile);
// Try to get full path and extract icon
// If we can't open a process with PROCESS_QUERY_LIMITED_INFORMATION then
// we for sure can't access their memory. - Skip in this case
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
if (hProcess)
{
@@ -140,7 +211,7 @@ void ProcessPicker::enumerateProcesses()
GetModuleFileNameExW(hProcess, nullptr, path, pathLen))
{
info.path = QString::fromWCharArray(path);
// Extract icon from executable
SHFILEINFOW sfi = {};
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
@@ -158,11 +229,16 @@ void ProcessPicker::enumerateProcesses()
{
info.path = "";
}
// Detect 32-bit (WoW64) process
BOOL isWow64 = FALSE;
if (IsWow64Process(hProcess, &isWow64) && isWow64)
info.is32Bit = true;
CloseHandle(hProcess);
processes.append(info);
}
} while (Process32NextW(snapshot, &pe32));
}
@@ -204,6 +280,16 @@ void ProcessPicker::enumerateProcesses()
info.name = procName;
info.path = resolvedPath;
info.icon = defaultIcon;
// Detect 32-bit ELF process
QFile exeFile(exePath);
if (exeFile.open(QIODevice::ReadOnly)) {
QByteArray header = exeFile.read(5);
if (header.size() >= 5 && header[4] == 1) // ELFCLASS32
info.is32Bit = true;
exeFile.close();
}
processes.append(info);
}
#else
@@ -227,11 +313,16 @@ void ProcessPicker::populateTable(const QList<ProcessInfo>& processes)
pidItem->setData(Qt::EditRole, (int)proc.pid);
ui->processTable->setItem(i, 0, pidItem);
// Name column with icon
auto* nameItem = new QTableWidgetItem(proc.name);
// Name column with icon and architecture indicator
QString displayName = proc.is32Bit
? proc.name + QStringLiteral(" (32-bit)")
: proc.name;
auto* nameItem = new QTableWidgetItem(displayName);
if (!proc.icon.isNull()) {
nameItem->setIcon(proc.icon);
}
// Store original name for selectedProcessName()
nameItem->setData(Qt::UserRole, proc.name);
ui->processTable->setItem(i, 1, nameItem);
// Path column with tooltip for full path
@@ -239,6 +330,9 @@ void ProcessPicker::populateTable(const QList<ProcessInfo>& processes)
pathItem->setToolTip(proc.path); // Show full path on hover
ui->processTable->setItem(i, 2, pathItem);
}
// Default sort: highest PID first (most recently launched processes on top)
ui->processTable->sortItems(0, Qt::DescendingOrder);
}
void ProcessPicker::filterProcesses(const QString& text)
@@ -269,3 +363,22 @@ void ProcessPicker::applyFilter()
populateTable(filtered);
}
void ProcessPicker::selectPreferredProcess()
{
// Try to select the last-attached process if it's in the list
QSettings s("Reclass", "Reclass");
QString lastProc = s.value("lastAttachedProcess").toString();
if (lastProc.isEmpty()) return;
for (int row = 0; row < ui->processTable->rowCount(); ++row) {
auto* nameItem = ui->processTable->item(row, 1);
if (!nameItem) continue;
QString name = nameItem->data(Qt::UserRole).toString();
if (name.compare(lastProc, Qt::CaseInsensitive) == 0) {
ui->processTable->selectRow(row);
ui->processTable->scrollToItem(nameItem);
break;
}
}
}

View File

@@ -14,6 +14,7 @@ struct ProcessInfo {
QString name;
QString path;
QIcon icon;
bool is32Bit = false;
};
class ProcessPicker : public QDialog
@@ -34,9 +35,11 @@ private slots:
void filterProcesses(const QString& text);
private:
void initUi();
void enumerateProcesses();
void populateTable(const QList<ProcessInfo>& processes);
void applyFilter();
void selectPreferredProcess();
Ui::ProcessPicker *ui;
uint32_t m_selectedPid = 0;

View File

@@ -127,22 +127,6 @@
</widget>
<resources/>
<connections>
<connection>
<sender>attachButton</sender>
<signal>clicked()</signal>
<receiver>ProcessPicker</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>600</x>
<y>470</y>
</hint>
<hint type="destinationlabel">
<x>350</x>
<y>250</y>
</hint>
</hints>
</connection>
<connection>
<sender>cancelButton</sender>
<signal>clicked()</signal>

View File

@@ -1,12 +1,16 @@
#include "providerregistry.h"
#include <QDebug>
#include <QMenu>
#include <QIcon>
#include <QHash>
ProviderRegistry& ProviderRegistry::instance() {
static ProviderRegistry s_instance;
return s_instance;
}
void ProviderRegistry::registerProvider(const QString& name, const QString& identifier, IProviderPlugin* plugin) {
void ProviderRegistry::registerProvider(const QString& name, const QString& identifier,
IProviderPlugin* plugin, const QString& dllFileName) {
// Check if already registered
for (const auto& info : m_providers) {
if (info.identifier == identifier) {
@@ -14,8 +18,8 @@ void ProviderRegistry::registerProvider(const QString& name, const QString& iden
return;
}
}
m_providers.append(ProviderInfo(name, identifier, plugin));
m_providers.append(ProviderInfo(name, identifier, plugin, dllFileName));
qDebug() << "ProviderRegistry: Registered plugin provider:" << name << "(" << identifier << ")";
}
@@ -55,3 +59,57 @@ const ProviderRegistry::ProviderInfo* ProviderRegistry::findProvider(const QStri
void ProviderRegistry::clear() {
m_providers.clear();
}
void ProviderRegistry::populateSourceMenu(QMenu* menu,
const QVector<SavedSourceDisplay>& savedSources)
{
static const QHash<QString, QString> s_providerIcons = {
{QStringLiteral("processmemory"), QStringLiteral(":/vsicons/server-process.svg")},
{QStringLiteral("remoteprocessmemory"), QStringLiteral(":/vsicons/remote.svg")},
{QStringLiteral("windbgmemory"), QStringLiteral(":/vsicons/debug.svg")},
{QStringLiteral("reclass.netcompatlayer"), QStringLiteral(":/vsicons/plug.svg")},
};
// File source
auto* fileAct = menu->addAction(QIcon(QStringLiteral(":/vsicons/file-binary.svg")),
QStringLiteral("File"));
fileAct->setIconVisibleInMenu(true);
fileAct->setData(QStringLiteral("File"));
// Registered providers
const auto& providers = instance().providers();
for (const auto& prov : providers) {
auto it = s_providerIcons.constFind(prov.identifier);
QIcon icon(it != s_providerIcons.constEnd() ? *it
: QStringLiteral(":/vsicons/extensions.svg"));
QString label = prov.dllFileName.isEmpty()
? prov.name
: QStringLiteral("%1 (%2)").arg(prov.name, prov.dllFileName);
auto* act = menu->addAction(icon, label);
act->setIconVisibleInMenu(true);
act->setData(prov.name); // routing key for selectSource()
// Plugin-specific actions (e.g. "Unload Driver" when loaded)
if (prov.plugin)
prov.plugin->populatePluginMenu(menu);
}
// Saved sources
if (!savedSources.isEmpty()) {
menu->addSeparator();
for (int i = 0; i < savedSources.size(); i++) {
auto* act = menu->addAction(savedSources[i].text);
act->setCheckable(true);
act->setChecked(savedSources[i].active);
act->setData(QStringLiteral("#saved:%1").arg(i));
}
menu->addSeparator();
auto* clearAct = menu->addAction(
QIcon(QStringLiteral(":/vsicons/clear-all.svg")),
QStringLiteral("Clear All"));
clearAct->setIconVisibleInMenu(true);
clearAct->setData(QStringLiteral("#clear"));
}
}

View File

@@ -7,6 +7,13 @@
// Forward declarations
namespace rcx { class Provider; }
class QWidget;
class QMenu;
// Lightweight struct for saved source display in menus
struct SavedSourceDisplay {
QString text;
bool active = false;
};
/**
* Global registry for data source providers
@@ -25,10 +32,13 @@ public:
IProviderPlugin* plugin; // Plugin (if plugin-based)
BuiltinFactory factory; // Factory (if built-in)
bool isBuiltin;
ProviderInfo(const QString& n, const QString& id, IProviderPlugin* p)
: name(n), identifier(id), plugin(p), factory(nullptr), isBuiltin(false) {}
QString dllFileName; // Original DLL/SO filename (plugin-based only)
ProviderInfo(const QString& n, const QString& id, IProviderPlugin* p,
const QString& dll = {})
: name(n), identifier(id), plugin(p), factory(nullptr),
isBuiltin(false), dllFileName(dll) {}
ProviderInfo(const QString& n, const QString& id, BuiltinFactory f)
: name(n), identifier(id), plugin(nullptr), factory(f), isBuiltin(true) {}
};
@@ -36,7 +46,8 @@ public:
static ProviderRegistry& instance();
// Register a plugin-based provider
void registerProvider(const QString& name, const QString& identifier, IProviderPlugin* plugin);
void registerProvider(const QString& name, const QString& identifier, IProviderPlugin* plugin,
const QString& dllFileName = {});
// Register a built-in provider with a factory function
void registerBuiltinProvider(const QString& name, const QString& identifier, BuiltinFactory factory);
@@ -52,7 +63,13 @@ public:
// Clear all providers
void clear();
// Populate a QMenu with source items (File, providers with icons/dll names,
// plugin actions, saved sources). Used by both the main window Data Source
// menu and the RcxEditor inline source picker.
static void populateSourceMenu(QMenu* menu,
const QVector<SavedSourceDisplay>& savedSources = {});
private:
ProviderRegistry() = default;
QList<ProviderInfo> m_providers;

View File

@@ -1,11 +1,28 @@
#pragma once
#include <QByteArray>
#include <QString>
#include <QVector>
#include <cstdint>
#include <cstring>
namespace rcx {
struct MemoryRegion {
uint64_t base = 0;
uint64_t size = 0;
bool readable = true;
bool writable = false;
bool executable = false;
QString moduleName;
};
struct VtopResult {
uint64_t physical = 0;
uint64_t pml4e = 0, pdpte = 0, pde = 0, pte = 0;
uint8_t pageSize = 0; // 0=4KB, 1=2MB, 2=1GB
bool valid = false;
};
class Provider {
public:
virtual ~Provider() = default;
@@ -33,10 +50,14 @@ public:
// Examples: "File", "Process", "Socket"
virtual QString kind() const { return QStringLiteral("File"); }
// Base address for providers that offset reads (e.g. process memory).
// Native pointer size of the target (4 for 32-bit, 8 for 64-bit).
// Providers should override this to report the target's architecture.
virtual int pointerSize() const { return 8; }
// Initial base address discovered by the provider (e.g. main module base).
// Used by the controller to set tree.baseAddress on first attach.
// For file/buffer providers this is always 0.
virtual uint64_t base() const { return 0; }
virtual void setBase(uint64_t newBase) { Q_UNUSED(newBase); }
// Resolve an absolute address to a symbol name.
// Returns empty string if no symbol is known.
@@ -47,6 +68,41 @@ public:
return {};
}
// Resolve a module/symbol name to its address (reverse of getSymbol).
// Returns 0 if the name is not found.
virtual uint64_t symbolToAddress(const QString& name) const {
Q_UNUSED(name);
return 0;
}
// Enumerate committed/readable memory regions.
// Used by the scan engine to know what address ranges to scan.
// Default: returns empty (scan engine falls back to [0, size())).
virtual QVector<MemoryRegion> enumerateRegions() const { return {}; }
// Process Environment Block address (x64 PEB VA in target process).
// Only meaningful for live process providers. Returns 0 if unavailable.
virtual uint64_t peb() const { return 0; }
struct ThreadInfo { uint64_t tebAddress; uint32_t threadId; };
virtual QVector<ThreadInfo> tebs() const { return {}; }
struct ModuleEntry { QString name; QString fullPath; uint64_t base; uint64_t size; };
virtual QVector<ModuleEntry> enumerateModules() const { return {}; }
// --- Kernel paging capabilities (override in kernel providers) ---
virtual bool hasKernelPaging() const { return false; }
virtual uint64_t getCr3() const { return 0; }
virtual VtopResult translateAddress(uint64_t va) const {
Q_UNUSED(va); return {};
}
virtual QVector<uint64_t> readPageTable(uint64_t physAddr,
int startIdx = 0,
int count = 512) const {
Q_UNUSED(physAddr); Q_UNUSED(startIdx); Q_UNUSED(count);
return {};
}
// --- Derived convenience (non-virtual, never override) ---
bool isValid() const { return size() > 0; }

View File

@@ -53,6 +53,7 @@ public:
bool isReadable(uint64_t addr, int len) const override {
if (len <= 0) return (len == 0);
uint64_t end = addr + static_cast<uint64_t>(len);
if (end < addr) return false; // overflow
for (uint64_t p = addr & kPageMask; p < end; p += kPageSize) {
if (!m_pages.contains(p)) return false;
}
@@ -67,6 +68,9 @@ public:
QString getSymbol(uint64_t addr) const override {
return m_real ? m_real->getSymbol(addr) : QString();
}
uint64_t symbolToAddress(const QString& n) const override {
return m_real ? m_real->symbolToAddress(n) : 0;
}
bool write(uint64_t addr, const void* buf, int len) override {
if (!m_real) return false;

173
src/rcxtooltip.h Normal file
View File

@@ -0,0 +1,173 @@
#pragma once
#include <QWidget>
#include <QPainter>
#include <QPainterPath>
#include <QScreen>
#include <QApplication>
#include <QMouseEvent>
#include <functional>
namespace rcx {
// ── Modern arrow tooltip ──
// Draws a rounded-rect body with a triangular arrow whose tip touches
// the anchor point (center of the dwell area).
//
// Bypasses Fusion/CSS/DWM entirely — everything is manual QPainter on a
// WA_TranslucentBackground layered window. The DarkTitleBar property is
// pre-set to prevent DarkApp::notify from calling DwmSetWindowAttribute
// (which was the root cause of the previous transparent-window failure).
//
// Usage:
// tip->setTheme(bg, border, titleCol, bodyCol, sepCol);
// tip->populate("Title", "line1\nline2", font);
// tip->showAt(QPoint(midX, lineBottom)); // arrow tip at this point
// tip->dismiss();
class RcxTooltip : public QWidget {
public:
static constexpr int kArrowH = 8;
static constexpr int kArrowW = 14;
static constexpr int kRadius = 6;
static constexpr int kPad = 10;
static constexpr int kGap = 4;
static constexpr int kMaxW = 550;
std::function<void(QMouseEvent*)> onMouseMove;
explicit RcxTooltip(QWidget* parent = nullptr)
: QWidget(parent, Qt::ToolTip | Qt::FramelessWindowHint)
{
// ── Key fix: prevent DwmSetWindowAttribute on this window ──
// DarkApp::notify checks this property and skips DWM calls.
// Without this, DWMWA_USE_IMMERSIVE_DARK_MODE breaks WS_EX_LAYERED
// alpha compositing on Windows 10/11.
setProperty("DarkTitleBar", true);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_ShowWithoutActivating);
setAttribute(Qt::WA_DeleteOnClose, false);
setMouseTracking(true);
}
void setTheme(const QColor& bg, const QColor& border,
const QColor& title, const QColor& body, const QColor& sep) {
m_bg = bg; m_border = border;
m_titleCol = title; m_bodyCol = body; m_sepCol = sep;
}
void populate(const QString& title, const QString& body, const QFont& font) {
if (title == m_title && body == m_body && isVisible()) return;
m_title = title; m_body = body;
m_lines = body.split('\n');
m_font = font;
m_font.setPointSizeF(font.pointSizeF() * 0.9);
m_bold = m_font; m_bold.setBold(true);
recalc();
}
// `anchor`: global screen point where the arrow tip touches.
// Typically the center-bottom of the hovered span.
void showAt(const QPoint& anchor) {
QRect scr = screenAt(anchor);
int w = m_bw, h = m_bh + kArrowH;
m_up = (anchor.y() + h <= scr.bottom());
int x = qBound(scr.left() + 2, anchor.x() - w / 2, scr.right() - w - 2);
int y = m_up ? anchor.y() : anchor.y() - h;
m_ax = qBound(kRadius + kArrowW/2 + 1, anchor.x() - x,
w - kRadius - kArrowW/2 - 1);
setFixedSize(w, h);
move(x, y);
if (!isVisible()) show();
update();
}
void dismiss() { if (isVisible()) hide(); }
protected:
void paintEvent(QPaintEvent*) override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// Body rect (excludes arrow space)
QRectF b(0.5, m_up ? kArrowH + 0.5 : 0.5,
width() - 1.0, m_bh - 1.0);
qreal r = kRadius, ax = m_ax, ah = kArrowW / 2.0;
// ── Single contiguous path: rounded rect + arrow notch ──
// No QPainterPath::united() — that causes junction artifacts.
// Clockwise from top-left, inserting the arrow inline.
QPainterPath pp;
pp.moveTo(b.left() + r, b.top());
if (m_up) {
pp.lineTo(ax - ah, b.top());
pp.lineTo(ax, 0.5);
pp.lineTo(ax + ah, b.top());
}
pp.lineTo(b.right() - r, b.top());
pp.arcTo(b.right() - 2*r, b.top(), 2*r, 2*r, 90, -90);
pp.lineTo(b.right(), b.bottom() - r);
pp.arcTo(b.right() - 2*r, b.bottom() - 2*r, 2*r, 2*r, 0, -90);
if (!m_up) {
pp.lineTo(ax + ah, b.bottom());
pp.lineTo(ax, height() - 0.5);
pp.lineTo(ax - ah, b.bottom());
}
pp.lineTo(b.left() + r, b.bottom());
pp.arcTo(b.left(), b.bottom() - 2*r, 2*r, 2*r, 270, -90);
pp.lineTo(b.left(), b.top() + r);
pp.arcTo(b.left(), b.top(), 2*r, 2*r, 180, -90);
pp.closeSubpath();
p.setPen(QPen(m_border, 1));
p.setBrush(m_bg);
p.drawPath(pp);
// ── Content: title + separator + body ──
qreal cy = (m_up ? kArrowH : 0) + kPad;
QFontMetrics tf(m_bold), bf(m_font);
if (!m_title.isEmpty()) {
p.setFont(m_bold); p.setPen(m_titleCol);
p.drawText(QPointF(kPad, cy + tf.ascent()), m_title);
cy += tf.height() + kGap;
p.setPen(m_sepCol);
p.drawLine(QPointF(kPad, cy), QPointF(width() - kPad, cy));
cy += 1 + kGap;
}
p.setFont(m_font); p.setPen(m_bodyCol);
for (const auto& l : m_lines) {
p.drawText(QPointF(kPad, cy + bf.ascent()), l);
cy += bf.lineSpacing();
}
}
void mouseMoveEvent(QMouseEvent* e) override {
if (onMouseMove) onMouseMove(e); else QWidget::mouseMoveEvent(e);
}
private:
static QRect screenAt(const QPoint& pt) {
auto* s = QApplication::screenAt(pt);
return s ? s->availableGeometry() : QRect(0, 0, 1920, 1080);
}
void recalc() {
QFontMetrics tf(m_bold), bf(m_font);
int maxW = m_title.isEmpty() ? 0 : tf.horizontalAdvance(m_title);
for (const auto& l : m_lines) maxW = qMax(maxW, bf.horizontalAdvance(l));
m_bw = qMin(maxW + 2 * kPad, kMaxW);
m_bh = kPad + (m_title.isEmpty() ? 0 : tf.height() + kGap + 1 + kGap)
+ m_lines.size() * bf.lineSpacing() + kPad;
}
QString m_title, m_body;
QStringList m_lines;
QFont m_font, m_bold;
QColor m_bg{30, 30, 30}, m_border{60, 60, 60};
QColor m_titleCol{220, 220, 220}, m_bodyCol{180, 180, 180}, m_sepCol{60, 60, 60};
bool m_up = true;
int m_ax = 0, m_bw = 0, m_bh = 0;
};
} // namespace rcx

View File

@@ -17,6 +17,7 @@
<file alias="file-binary.svg">vsicons/file-binary.svg</file>
<file alias="debug.svg">vsicons/debug.svg</file>
<file alias="close.svg">vsicons/close.svg</file>
<file alias="cloud-download.svg">vsicons/cloud-download.svg</file>
<file alias="arrow-left.svg">vsicons/arrow-left.svg</file>
<file alias="arrow-right.svg">vsicons/arrow-right.svg</file>
<file alias="split-horizontal.svg">vsicons/split-horizontal.svg</file>
@@ -49,7 +50,24 @@
<file alias="symbol-ruler.svg">vsicons/symbol-ruler.svg</file>
<file alias="settings-gear.svg">vsicons/settings-gear.svg</file>
<file alias="chevron-down.svg">vsicons/chevron-down.svg</file>
<file alias="chevron-right.svg">vsicons/chevron-right.svg</file>
<file alias="chevron-left.svg">vsicons/chevron-left.svg</file>
<file alias="folder.svg">vsicons/folder.svg</file>
<file alias="symbol-enum.svg">vsicons/symbol-enum.svg</file>
<file alias="symbol-class.svg">vsicons/symbol-class.svg</file>
<file alias="symbol-variable.svg">vsicons/symbol-variable.svg</file>
<file alias="symbol-method.svg">vsicons/symbol-method.svg</file>
<file alias="server-process.svg">vsicons/server-process.svg</file>
<file alias="remote.svg">vsicons/remote.svg</file>
<file alias="plug.svg">vsicons/plug.svg</file>
<file alias="clear-all.svg">vsicons/clear-all.svg</file>
<file alias="search.svg">vsicons/search.svg</file>
<file alias="regex.svg">vsicons/regex.svg</file>
<file alias="refresh.svg">vsicons/refresh.svg</file>
<file alias="pin.svg">vsicons/pin.svg</file>
<file alias="pinned.svg">vsicons/pinned.svg</file>
<file alias="close-all.svg">vsicons/close-all.svg</file>
<file alias="split-vertical.svg">vsicons/split-vertical.svg</file>
<file alias="book.svg">vsicons/book.svg</file>
</qresource>
</RCC>

Some files were not shown because too many files have changed in this diff Show More