Compare commits
200 Commits
plugin-sys
...
snapshot-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a716444f4 | ||
|
|
a46da4ee16 | ||
|
|
cd52451210 | ||
|
|
82bf9118c9 | ||
|
|
f4c7e9327d | ||
|
|
5944dbdc81 | ||
|
|
b3425aec9e | ||
|
|
2a8cfee719 | ||
|
|
e999c664b8 | ||
|
|
0dc4af6b1d | ||
|
|
376aad2169 | ||
|
|
4937c58062 | ||
|
|
9c72265901 | ||
|
|
86499e58ee | ||
|
|
b2ae8d5a5d | ||
|
|
6768f04e9a | ||
|
|
c6e5f6508f | ||
|
|
e6529052b3 | ||
|
|
d43e989992 | ||
|
|
879e9f4047 | ||
|
|
e0d5a799b4 | ||
|
|
efae193520 | ||
|
|
ba1c2f8e5a | ||
|
|
5a0a4d1802 | ||
|
|
030eb34510 | ||
|
|
2939b25895 | ||
|
|
d38cb02fa2 | ||
|
|
9f285b37b2 | ||
|
|
cae599a0c6 | ||
|
|
d0734ba8be | ||
|
|
696ff044ac | ||
|
|
da312ccac6 | ||
|
|
552b45b16c | ||
|
|
e89fd4a6c1 | ||
|
|
7524004b32 | ||
|
|
ed8a44917b | ||
|
|
ecfac3decf | ||
|
|
851d744263 | ||
|
|
41e2f9f662 | ||
|
|
95faf027a9 | ||
|
|
6a51c904de | ||
|
|
0d73575ea7 | ||
|
|
aa04cfcb5c | ||
|
|
1465e7fbed | ||
|
|
52f751e751 | ||
|
|
0a19789a9d | ||
|
|
62a68bef80 | ||
|
|
4941f860b6 | ||
|
|
c45d51d736 | ||
|
|
5b46065403 | ||
|
|
4706f7b782 | ||
|
|
fe9bfafa3b | ||
|
|
ff928df685 | ||
|
|
d6e3c182fc | ||
|
|
078a6028f0 | ||
|
|
d7a6e1862e | ||
|
|
1ddf47a754 | ||
|
|
1a885a8b1d | ||
|
|
67218d3e48 | ||
|
|
f651edd740 | ||
|
|
25aaace382 | ||
|
|
b5ddb042b8 | ||
|
|
e900dea836 | ||
|
|
b647a334bc | ||
|
|
fc390bc1f7 | ||
|
|
7efe740ec1 | ||
|
|
48409d1d38 | ||
|
|
df1435d9b7 | ||
|
|
5e11ff5496 | ||
|
|
22842d9801 | ||
|
|
50acde60cb | ||
|
|
1d7d384b93 | ||
|
|
3a76b03c85 | ||
|
|
ac94855d6c | ||
|
|
d65b6c5a29 | ||
|
|
d45ee9e4c9 | ||
|
|
31115014a5 | ||
|
|
8e88d588be | ||
|
|
b089e20d36 | ||
|
|
5fa1dd0ab4 | ||
|
|
3b1fe7ff35 | ||
|
|
4595b366e3 | ||
|
|
33d7dc74cb | ||
|
|
e118231bb1 | ||
|
|
0cfd7ad87a | ||
|
|
2d3ce63b54 | ||
|
|
0e087fa3a4 | ||
|
|
c7afe363f3 | ||
|
|
2a44d2ac57 | ||
|
|
d989e2a947 | ||
|
|
7678da033d | ||
|
|
acc3ebf5db | ||
|
|
26217f5de8 | ||
|
|
fa0d9a377b | ||
|
|
b1d3e52204 | ||
|
|
1cccd320b0 | ||
|
|
5b6e0473cb | ||
|
|
57d55456a8 | ||
|
|
bb466516ba | ||
|
|
444ba34fa3 | ||
|
|
91633169a0 | ||
|
|
f041761b62 | ||
|
|
1c3b4af045 | ||
|
|
5ae9ca0979 | ||
|
|
e064646c02 | ||
|
|
c6c56ffaee | ||
|
|
aba8e5cac9 | ||
|
|
3a5d03fae0 | ||
|
|
df79da54e3 | ||
|
|
e3ff4dfe71 | ||
|
|
735e4ea9f7 | ||
|
|
d937d2f42e | ||
|
|
3685530287 | ||
|
|
9e90f66ca0 | ||
|
|
f53fa84a15 | ||
|
|
13e28e8791 | ||
|
|
079b3121ce | ||
|
|
5e40349768 | ||
|
|
8dd6110ec6 | ||
|
|
eb27fc7988 | ||
|
|
85994d68b9 | ||
|
|
55dc5d5875 | ||
|
|
3a92336132 | ||
|
|
f9b33f2ba7 | ||
|
|
f2dab07870 | ||
|
|
9d22a5ed69 | ||
|
|
193ab81ecf | ||
|
|
aa0840b332 | ||
|
|
f3631f17ff | ||
|
|
42e9bde7ba | ||
|
|
07fedf0ae8 | ||
|
|
2e02a01495 | ||
|
|
71bc51cbab | ||
|
|
60a97ab81b | ||
|
|
bb00e75019 | ||
|
|
c038c59e34 | ||
|
|
862f76b984 | ||
|
|
818285a76e | ||
|
|
ef5e2ebdb9 | ||
|
|
75fedd2222 | ||
|
|
389745e501 | ||
|
|
1473a58742 | ||
|
|
4192a4dad3 | ||
|
|
4c6bb9564f | ||
|
|
0ef9841f90 | ||
|
|
0a8244dad4 | ||
|
|
c856ba2697 | ||
|
|
b44dc9e96b | ||
|
|
0f2ded471f | ||
|
|
c9377c3afd | ||
|
|
a86912add1 | ||
|
|
5a9a6b754f | ||
|
|
0df52e82b8 | ||
|
|
9a342286ee | ||
|
|
1af0e17ef8 | ||
|
|
a45d66dd4e | ||
|
|
6922166e3c | ||
|
|
ffde3343dd | ||
|
|
c86a6dbc73 | ||
|
|
b153665059 | ||
|
|
a88b584ca0 | ||
|
|
3e827194b8 | ||
|
|
9f1c85913c | ||
|
|
cb151ab850 | ||
|
|
b0aa7cda67 | ||
|
|
4b1d3e9d3f | ||
|
|
e73b783cda | ||
|
|
7e0b995f4d | ||
|
|
52d65b4a23 | ||
|
|
db5d3ae311 | ||
|
|
33a093ae7d | ||
|
|
968476b65a | ||
|
|
df07b61144 | ||
|
|
3db051f4ba | ||
|
|
fc48fd6d2d | ||
|
|
0ffb7d6f58 | ||
|
|
0b0d9f23e1 | ||
|
|
7194322831 | ||
|
|
5f1fd56171 | ||
|
|
6bd61a6b78 | ||
|
|
60990362a0 | ||
|
|
55ef83a39f | ||
|
|
e034fe6f6c | ||
|
|
2e387f2dfc | ||
|
|
4295460597 | ||
|
|
df566064ba | ||
|
|
24a7e68136 | ||
|
|
8eab304538 | ||
|
|
9dd104ff34 | ||
|
|
910b607b79 | ||
|
|
c415b11825 | ||
|
|
1d6fddb51e | ||
|
|
276dcae444 | ||
|
|
659fb7bd32 | ||
|
|
85b840379d | ||
|
|
f4149faa9a | ||
|
|
b6c713eb29 | ||
|
|
4029b05298 | ||
|
|
0e65b9997e | ||
|
|
4caa7daa44 |
231
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install Qt6 and MinGW
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
version: "6.8.1"
|
||||||
|
arch: "win64_mingw"
|
||||||
|
tools: "tools_mingw1310,qt.tools.win64_mingw1310"
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
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: 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/
|
||||||
|
cp build/*.dll release/ 2>/dev/null || true
|
||||||
|
cp -r build/platforms release/ 2>/dev/null || true
|
||||||
|
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 -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 artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Reclass-win64-qt6
|
||||||
|
path: Reclass-win64-qt6.zip
|
||||||
|
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install Qt6
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
version: "6.8.1"
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
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 -DBUILD_UI_TESTS=OFF
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
|
- name: Create AppImage
|
||||||
|
run: |
|
||||||
|
# Download linuxdeploy and Qt plugin
|
||||||
|
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||||
|
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||||
|
chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage
|
||||||
|
|
||||||
|
# Build AppDir structure
|
||||||
|
mkdir -p AppDir/usr/bin AppDir/usr/share/icons/hicolor/256x256/apps
|
||||||
|
cp build/Reclass AppDir/usr/bin/
|
||||||
|
cp build/ReclassMcpBridge AppDir/usr/bin/
|
||||||
|
cp -r build/themes AppDir/usr/bin/ 2>/dev/null || true
|
||||||
|
cp -r build/examples AppDir/usr/bin/ 2>/dev/null || true
|
||||||
|
mkdir -p AppDir/usr/bin/Plugins
|
||||||
|
cp build/Plugins/*.so AppDir/usr/bin/Plugins/ 2>/dev/null || true
|
||||||
|
cp src/icons/class.png AppDir/usr/share/icons/hicolor/256x256/apps/reclass.png
|
||||||
|
|
||||||
|
# Create AppImage with Qt libs bundled
|
||||||
|
# install-qt-action adds Qt bin to PATH; find qmake there
|
||||||
|
QMAKE_BIN=$(which qmake 2>/dev/null || which qmake6 2>/dev/null || find "$RUNNER_WORKSPACE" -name qmake -path "*/bin/*" | head -1)
|
||||||
|
echo "Found qmake at: $QMAKE_BIN"
|
||||||
|
export QMAKE="$QMAKE_BIN"
|
||||||
|
QT_ROOT=$(dirname "$(dirname "$QMAKE_BIN")")
|
||||||
|
export LD_LIBRARY_PATH="$QT_ROOT/lib:$LD_LIBRARY_PATH"
|
||||||
|
export EXTRA_QT_PLUGINS="svg;iconengines"
|
||||||
|
./linuxdeploy-x86_64.AppImage --appdir AppDir \
|
||||||
|
--desktop-file deploy/Reclass.desktop \
|
||||||
|
--icon-file AppDir/usr/share/icons/hicolor/256x256/apps/reclass.png \
|
||||||
|
--plugin qt \
|
||||||
|
--output appimage
|
||||||
|
# Rename to final name
|
||||||
|
ls Reclass-*.AppImage
|
||||||
|
mv Reclass-*.AppImage Reclass-linux64-qt6.AppImage
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
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
|
||||||
|
id: date
|
||||||
|
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
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: |
|
||||||
|
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
@@ -11,3 +11,7 @@ build/
|
|||||||
*.suo
|
*.suo
|
||||||
.vs/
|
.vs/
|
||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
|
plugins/RcNetPluginCompatLayer/bridge/obj
|
||||||
|
plugins/RcNetPluginCompatLayer/bridge/bin
|
||||||
|
.cache
|
||||||
|
*.DS_Store
|
||||||
|
|||||||
6
.gitmodules
vendored
@@ -1,3 +1,9 @@
|
|||||||
[submodule "third_party/qscintilla"]
|
[submodule "third_party/qscintilla"]
|
||||||
path = third_party/qscintilla
|
path = third_party/qscintilla
|
||||||
url = https://github.com/brCreate/QScintilla.git
|
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
|
||||||
|
|||||||
522
CMakeLists.txt
@@ -1,18 +1,71 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(ReclassX VERSION 0.1 LANGUAGES CXX)
|
project(Reclass VERSION 0.1 LANGUAGES C CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets PrintSupport Svg Concurrent)
|
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
# Find Qt6 or Qt5 (config mode first, then FindQt5.cmake module for auto-download)
|
||||||
|
set(_QT_COMPONENTS Core Widgets PrintSupport Svg Concurrent Network)
|
||||||
|
find_package(QT NAMES Qt6 Qt5 COMPONENTS ${_QT_COMPONENTS} QUIET)
|
||||||
|
if(NOT QT_FOUND)
|
||||||
|
find_package(Qt5 REQUIRED COMPONENTS ${_QT_COMPONENTS})
|
||||||
|
set(QT_VERSION_MAJOR 5)
|
||||||
|
endif()
|
||||||
|
# The NAMES variant only detects the version; load the actual component targets
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${_QT_COMPONENTS})
|
||||||
|
set(QT Qt${QT_VERSION_MAJOR})
|
||||||
|
message(STATUS "Using ${QT}: ${${QT}_DIR}")
|
||||||
|
|
||||||
|
# Qt5 on Windows needs WinExtras for HICON conversion
|
||||||
|
set(_QT_WINEXTRAS "")
|
||||||
|
if(QT_VERSION_MAJOR EQUAL 5 AND WIN32)
|
||||||
|
find_package(Qt5 REQUIRED COMPONENTS WinExtras)
|
||||||
|
set(_QT_WINEXTRAS Qt5::WinExtras)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(QScintilla REQUIRED)
|
find_package(QScintilla REQUIRED)
|
||||||
|
|
||||||
add_executable(ReclassX
|
# 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/main.cpp
|
||||||
src/editor.h
|
src/editor.h
|
||||||
src/editor.cpp
|
src/editor.cpp
|
||||||
@@ -28,39 +81,117 @@ add_executable(ReclassX
|
|||||||
src/resources.qrc
|
src/resources.qrc
|
||||||
src/core.h
|
src/core.h
|
||||||
src/workspace_model.h
|
src/workspace_model.h
|
||||||
src/providers/buffer_provider.h src/providers/null_provider.h src/providers/process_provider.h src/providers/provider.h src/providers/snapshot_provider.h
|
src/providers/buffer_provider.h src/providers/null_provider.h src/providers/provider.h src/providers/snapshot_provider.h
|
||||||
src/providerregistry.cpp
|
src/providerregistry.cpp
|
||||||
src/providerregistry.h
|
src/providerregistry.h
|
||||||
src/pluginmanager.cpp
|
src/pluginmanager.cpp
|
||||||
src/pluginmanager.h
|
src/pluginmanager.h
|
||||||
|
src/typeselectorpopup.h
|
||||||
|
src/typeselectorpopup.cpp
|
||||||
|
src/themes/theme.h
|
||||||
|
src/themes/theme.cpp
|
||||||
|
src/themes/thememanager.h
|
||||||
|
src/themes/thememanager.cpp
|
||||||
|
src/themes/themeeditor.h
|
||||||
|
src/themes/themeeditor.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/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/disasm.h
|
||||||
|
src/disasm.cpp
|
||||||
|
third_party/fadec/decode.c
|
||||||
|
third_party/fadec/format.c
|
||||||
|
$<$<PLATFORM_ID:Windows>:src/app.rc>
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(ReclassX PRIVATE src)
|
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_link_libraries(ReclassX PRIVATE
|
target_include_directories(Reclass PRIVATE src third_party/fadec)
|
||||||
Qt6::Widgets
|
|
||||||
Qt6::PrintSupport
|
target_link_libraries(Reclass PRIVATE
|
||||||
Qt6::Svg
|
${QT}::Widgets
|
||||||
Qt6::Concurrent
|
${QT}::PrintSupport
|
||||||
|
${QT}::Svg
|
||||||
|
${QT}::Concurrent
|
||||||
|
${QT}::Network
|
||||||
QScintilla::QScintilla
|
QScintilla::QScintilla
|
||||||
dbghelp
|
${_QT_WINEXTRAS}
|
||||||
psapi
|
|
||||||
)
|
)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi raw_pdb)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_target(screenshot ALL
|
add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp)
|
||||||
COMMAND ReclassX --screenshot ${CMAKE_BINARY_DIR}/screenshot.png
|
target_link_libraries(ReclassMcpBridge PRIVATE ${QT}::Core ${QT}::Network)
|
||||||
DEPENDS ReclassX
|
if(APPLE)
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
add_custom_command(TARGET ReclassMcpBridge POST_BUILD
|
||||||
COMMENT "Capturing UI screenshot with class open..."
|
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
|
||||||
|
file(GLOB _theme_files "${CMAKE_SOURCE_DIR}/src/themes/defaults/*.json")
|
||||||
|
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()
|
||||||
|
|
||||||
|
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 to build directory
|
||||||
|
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
|
||||||
|
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()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
target_sources(Reclass PRIVATE ${_example_files})
|
||||||
|
set_source_files_properties(${_example_files}
|
||||||
|
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/examples")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(deploy)
|
||||||
|
|
||||||
add_custom_target(copy_demo ALL
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
${CMAKE_BINARY_DIR}/demo.rcx
|
|
||||||
${CMAKE_SOURCE_DIR}/src/examples/demo.rcx
|
|
||||||
DEPENDS screenshot
|
|
||||||
COMMENT "Copying demo.rcx to src/examples..."
|
|
||||||
)
|
|
||||||
|
|
||||||
set(_combine_script "${CMAKE_BINARY_DIR}/combine_sources.cmake")
|
set(_combine_script "${CMAKE_BINARY_DIR}/combine_sources.cmake")
|
||||||
file(WRITE ${_combine_script} "
|
file(WRITE ${_combine_script} "
|
||||||
@@ -77,7 +208,7 @@ foreach(_f
|
|||||||
\"${CMAKE_SOURCE_DIR}/src/generator.cpp\"
|
\"${CMAKE_SOURCE_DIR}/src/generator.cpp\"
|
||||||
\"${CMAKE_SOURCE_DIR}/src/main.cpp\")
|
\"${CMAKE_SOURCE_DIR}/src/main.cpp\")
|
||||||
file(READ \${_f} _content)
|
file(READ \${_f} _content)
|
||||||
file(APPEND \${_out} \${_content})
|
file(APPEND \${_out} \"\${_content}\")
|
||||||
file(APPEND \${_out} \"\\n\")
|
file(APPEND \${_out} \"\\n\")
|
||||||
endforeach()
|
endforeach()
|
||||||
message(STATUS \"Combined sources -> \${_out}\")
|
message(STATUS \"Combined sources -> \${_out}\")
|
||||||
@@ -85,95 +216,308 @@ message(STATUS \"Combined sources -> \${_out}\")
|
|||||||
|
|
||||||
add_custom_target(combined ALL
|
add_custom_target(combined ALL
|
||||||
COMMAND ${CMAKE_COMMAND} -P ${_combine_script}
|
COMMAND ${CMAKE_COMMAND} -P ${_combine_script}
|
||||||
DEPENDS ReclassX
|
DEPENDS Reclass
|
||||||
COMMENT "Combining all source files into h_cpp_combined.txt"
|
COMMENT "Combining all source files into h_cpp_combined.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
if(BUILD_TESTING)
|
if(BUILD_TESTING)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Test)
|
find_package(${QT} REQUIRED COMPONENTS Test)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
add_executable(test_core tests/test_core.cpp src/format.cpp src/compose.cpp)
|
# Disasm/Fadec sources needed by any test that links editor.cpp
|
||||||
|
set(DISASM_SRCS src/disasm.cpp third_party/fadec/decode.c third_party/fadec/format.c)
|
||||||
|
|
||||||
|
# ── 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 src/addressparser.cpp)
|
||||||
target_include_directories(test_core PRIVATE src)
|
target_include_directories(test_core PRIVATE src)
|
||||||
target_link_libraries(test_core PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_core PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
add_test(NAME test_core COMMAND test_core)
|
add_test(NAME test_core COMMAND test_core)
|
||||||
|
|
||||||
add_executable(test_format tests/test_format.cpp src/format.cpp)
|
add_executable(test_format tests/test_format.cpp src/format.cpp src/addressparser.cpp)
|
||||||
target_include_directories(test_format PRIVATE src)
|
target_include_directories(test_format PRIVATE src)
|
||||||
target_link_libraries(test_format PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_format PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
add_test(NAME test_format COMMAND test_format)
|
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_include_directories(test_compose PRIVATE src)
|
||||||
target_link_libraries(test_compose PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_compose PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
add_test(NAME test_compose COMMAND test_compose)
|
add_test(NAME test_compose COMMAND test_compose)
|
||||||
|
|
||||||
add_executable(test_editor tests/test_editor.cpp src/editor.cpp src/compose.cpp src/format.cpp src/providerregistry.cpp)
|
|
||||||
target_include_directories(test_editor PRIVATE src)
|
|
||||||
target_link_libraries(test_editor PRIVATE
|
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Test
|
|
||||||
QScintilla::QScintilla)
|
|
||||||
add_test(NAME test_editor COMMAND test_editor)
|
|
||||||
|
|
||||||
add_executable(test_provider tests/test_provider.cpp)
|
add_executable(test_provider tests/test_provider.cpp)
|
||||||
target_include_directories(test_provider PRIVATE src)
|
target_include_directories(test_provider PRIVATE src)
|
||||||
target_link_libraries(test_provider PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_provider PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
add_test(NAME test_provider COMMAND test_provider)
|
add_test(NAME test_provider COMMAND test_provider)
|
||||||
|
|
||||||
add_executable(test_command_row tests/test_command_row.cpp)
|
add_executable(test_command_row tests/test_command_row.cpp)
|
||||||
target_include_directories(test_command_row PRIVATE src)
|
target_include_directories(test_command_row PRIVATE src)
|
||||||
target_link_libraries(test_command_row PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_command_row PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
add_test(NAME test_command_row COMMAND test_command_row)
|
add_test(NAME test_command_row COMMAND test_command_row)
|
||||||
|
|
||||||
add_executable(test_provider_getSymbol tests/test_provider_getSymbol.cpp)
|
|
||||||
target_include_directories(test_provider_getSymbol PRIVATE src)
|
|
||||||
target_link_libraries(test_provider_getSymbol PRIVATE Qt6::Core Qt6::Test)
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(test_provider_getSymbol PRIVATE psapi)
|
|
||||||
endif()
|
|
||||||
add_test(NAME test_provider_getSymbol COMMAND test_provider_getSymbol)
|
|
||||||
|
|
||||||
add_executable(test_controller tests/test_controller.cpp
|
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
|
||||||
target_include_directories(test_controller PRIVATE src)
|
|
||||||
target_link_libraries(test_controller PRIVATE
|
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
|
||||||
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
|
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
|
||||||
target_include_directories(test_validation PRIVATE src)
|
|
||||||
target_link_libraries(test_validation PRIVATE
|
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
|
||||||
add_test(NAME test_validation COMMAND test_validation)
|
|
||||||
|
|
||||||
add_executable(test_generator tests/test_generator.cpp
|
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_include_directories(test_generator PRIVATE src)
|
||||||
target_link_libraries(test_generator PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_generator PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
add_test(NAME test_generator COMMAND test_generator)
|
add_test(NAME test_generator COMMAND test_generator)
|
||||||
|
|
||||||
add_executable(test_context_menu tests/test_context_menu.cpp
|
add_executable(test_import_xml tests/test_import_xml.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
src/imports/import_reclass_xml.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
target_include_directories(test_import_xml PRIVATE src)
|
||||||
target_include_directories(test_context_menu PRIVATE src)
|
target_link_libraries(test_import_xml PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
target_link_libraries(test_context_menu PRIVATE
|
add_test(NAME test_import_xml COMMAND test_import_xml)
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
|
||||||
add_test(NAME test_context_menu COMMAND test_context_menu)
|
|
||||||
|
|
||||||
add_executable(test_new_features tests/test_new_features.cpp
|
add_executable(test_import_source tests/test_import_source.cpp
|
||||||
src/generator.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
src/imports/import_source.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
|
||||||
src/editor.cpp src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
target_include_directories(test_import_source PRIVATE src)
|
||||||
target_include_directories(test_new_features PRIVATE src)
|
target_link_libraries(test_import_source PRIVATE ${QT}::Core ${QT}::Test)
|
||||||
target_link_libraries(test_new_features PRIVATE
|
add_test(NAME test_import_source COMMAND test_import_source)
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
add_executable(test_export_xml tests/test_export_xml.cpp
|
||||||
add_test(NAME test_new_features COMMAND test_new_features)
|
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/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/addressparser.cpp src/controller.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
|
||||||
|
${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)
|
||||||
|
|
||||||
|
add_executable(test_validation tests/test_validation.cpp
|
||||||
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.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
|
||||||
|
${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)
|
||||||
|
|
||||||
|
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/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
|
||||||
|
${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)
|
||||||
|
|
||||||
|
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/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_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_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 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
|
||||||
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
|
||||||
|
QScintilla::QScintilla)
|
||||||
|
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 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_executable(test_new_features tests/test_new_features.cpp
|
||||||
|
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.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/addressparser.cpp src/controller.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
|
||||||
|
${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)
|
||||||
|
|
||||||
|
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/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
|
||||||
|
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)
|
||||||
|
|
||||||
|
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/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)
|
||||||
|
|
||||||
|
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_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)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
$<TARGET_FILE:test_controller>
|
||||||
|
DEPENDS test_controller
|
||||||
|
COMMENT "Deploying Qt runtime DLLs for tests..."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
endif() # BUILD_UI_TESTS
|
||||||
|
endif()
|
||||||
|
if(NOT APPLE)
|
||||||
|
add_subdirectory(plugins/ProcessMemory)
|
||||||
|
add_subdirectory(plugins/RemoteProcessMemory)
|
||||||
|
endif()
|
||||||
|
if(WIN32)
|
||||||
|
add_subdirectory(plugins/WinDbgMemory)
|
||||||
|
add_subdirectory(plugins/RcNetPluginCompatLayer)
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(plugins/ProcessMemory)
|
|
||||||
|
|||||||
187
README.md
@@ -1,16 +1,189 @@
|
|||||||
# ReclassX
|
<div align="center">
|
||||||
|
|
||||||
An improvement over other reclass like editors.
|
<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>
|
||||||
|
|
||||||
https://github.com/IChooseYou/ReclassX/raw/main/video.mp4
|
**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)
|
||||||
|
|
||||||
|
[](https://github.com/IChooseYou/Reclass/actions/workflows/build.yml)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://github.com/IChooseYou/Reclass/releases)
|
||||||
|
[]()
|
||||||
|
|
||||||
|
</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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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)
|
||||||
|
- **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
|
||||||
|
|
||||||
|
Extensible provider architecture via DLL plugins with `IPlugin` interface, factory function discovery, and auto/manual loading from a Plugins folder.
|
||||||
|
|
||||||
|
**Bundled plugins:**
|
||||||
|
|
||||||
|
| Plugin | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Process memory** | Attach to local processes on Windows and Linux — PID-based, with symbol resolution and module/region enumeration |
|
||||||
|
| **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
|
## Build
|
||||||
|
|
||||||
Requires Qt 6, QScintilla, and MinGW on Windows.
|
### Prerequisites
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
### Quick Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
|
||||||
|
cd Reclass
|
||||||
|
.\scripts\build_qscintilla.ps1
|
||||||
|
.\scripts\build.ps1
|
||||||
```
|
```
|
||||||
cmake -B build -G Ninja
|
|
||||||
cmake --build build
|
The build script auto-detects your Qt install location.
|
||||||
|
|
||||||
|
### 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](https://github.com/ReClassNET/ReClass.NET)
|
||||||
|
- [ReClassEx](https://github.com/ajkhoury/ReClassEx)
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<sub>MIT License</sub>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
set(_QSCI_ROOT "${CMAKE_SOURCE_DIR}/third_party/qscintilla")
|
set(_QSCI_ROOT "${CMAKE_SOURCE_DIR}/third_party/qscintilla")
|
||||||
|
|
||||||
|
# Try to find a pre-built library first
|
||||||
find_path(QScintilla_INCLUDE_DIR
|
find_path(QScintilla_INCLUDE_DIR
|
||||||
NAMES Qsci/qsciscintilla.h
|
NAMES Qsci/qsciscintilla.h
|
||||||
PATHS "${_QSCI_ROOT}/src" "${_QSCI_ROOT}/include"
|
PATHS "${_QSCI_ROOT}/src" "${_QSCI_ROOT}/include"
|
||||||
@@ -7,7 +8,10 @@ find_path(QScintilla_INCLUDE_DIR
|
|||||||
)
|
)
|
||||||
|
|
||||||
find_library(QScintilla_LIBRARY
|
find_library(QScintilla_LIBRARY
|
||||||
NAMES qscintilla2_qt6 libqscintilla2_qt6
|
NAMES
|
||||||
|
qscintilla2_qt${QT_VERSION_MAJOR} libqscintilla2_qt${QT_VERSION_MAJOR}
|
||||||
|
qscintilla2_qt6 libqscintilla2_qt6
|
||||||
|
qscintilla2_qt5 libqscintilla2_qt5
|
||||||
PATHS
|
PATHS
|
||||||
"${_QSCI_ROOT}/src/release"
|
"${_QSCI_ROOT}/src/release"
|
||||||
"${_QSCI_ROOT}/src"
|
"${_QSCI_ROOT}/src"
|
||||||
@@ -15,13 +19,11 @@ find_library(QScintilla_LIBRARY
|
|||||||
NO_DEFAULT_PATH
|
NO_DEFAULT_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
if(QScintilla_LIBRARY AND QScintilla_INCLUDE_DIR)
|
||||||
find_package_handle_standard_args(QScintilla DEFAULT_MSG
|
# Use pre-built library
|
||||||
QScintilla_LIBRARY QScintilla_INCLUDE_DIR)
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(QScintilla DEFAULT_MSG
|
||||||
if(QScintilla_FOUND)
|
QScintilla_LIBRARY QScintilla_INCLUDE_DIR)
|
||||||
set(QScintilla_INCLUDE_DIRS ${QScintilla_INCLUDE_DIR})
|
|
||||||
set(QScintilla_LIBRARIES ${QScintilla_LIBRARY})
|
|
||||||
if(NOT TARGET QScintilla::QScintilla)
|
if(NOT TARGET QScintilla::QScintilla)
|
||||||
add_library(QScintilla::QScintilla STATIC IMPORTED)
|
add_library(QScintilla::QScintilla STATIC IMPORTED)
|
||||||
set_target_properties(QScintilla::QScintilla PROPERTIES
|
set_target_properties(QScintilla::QScintilla PROPERTIES
|
||||||
@@ -29,4 +31,118 @@ if(QScintilla_FOUND)
|
|||||||
INTERFACE_INCLUDE_DIRECTORIES "${QScintilla_INCLUDE_DIR}"
|
INTERFACE_INCLUDE_DIRECTORIES "${QScintilla_INCLUDE_DIR}"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
elseif(EXISTS "${_QSCI_ROOT}/src/qsciscintilla.cpp")
|
||||||
|
# Build from source
|
||||||
|
message(STATUS "Building QScintilla from source")
|
||||||
|
|
||||||
|
file(GLOB _QSCI_LEXER_SOURCES "${_QSCI_ROOT}/scintilla/lexers/*.cpp")
|
||||||
|
file(GLOB _QSCI_LEXLIB_SOURCES "${_QSCI_ROOT}/scintilla/lexlib/*.cpp")
|
||||||
|
file(GLOB _QSCI_SCI_SOURCES "${_QSCI_ROOT}/scintilla/src/*.cpp")
|
||||||
|
file(GLOB _QSCI_HEADERS "${_QSCI_ROOT}/src/Qsci/*.h")
|
||||||
|
|
||||||
|
set(_QSCI_QT_SOURCES
|
||||||
|
"${_QSCI_ROOT}/src/qsciscintilla.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qsciscintillabase.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qsciabstractapis.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qsciapis.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscicommand.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscicommandset.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscidocument.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexer.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerasm.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexeravs.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerbash.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerbatch.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexercmake.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexercoffeescript.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexercpp.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexercsharp.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexercss.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexercustom.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerd.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerdiff.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexeredifact.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerfortran.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerfortran77.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerhex.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerhtml.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexeridl.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerintelhex.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerjava.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerjavascript.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerjson.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerlua.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexermakefile.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexermarkdown.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexermasm.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexermatlab.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexernasm.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexeroctave.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerpascal.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerperl.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerpostscript.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerpo.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerpov.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerproperties.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerpython.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerruby.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerspice.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexersql.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexersrec.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexertcl.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexertekhex.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexertex.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerverilog.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexervhdl.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexerxml.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscilexeryaml.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscimacro.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qsciprinter.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscistyle.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/qscistyledtext.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/InputMethod.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/ListBoxQt.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/PlatQt.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/SciAccessibility.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/SciClasses.cpp"
|
||||||
|
"${_QSCI_ROOT}/src/ScintillaQt.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(qscintilla2 STATIC
|
||||||
|
${_QSCI_QT_SOURCES}
|
||||||
|
${_QSCI_HEADERS}
|
||||||
|
${_QSCI_LEXER_SOURCES}
|
||||||
|
${_QSCI_LEXLIB_SOURCES}
|
||||||
|
${_QSCI_SCI_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(qscintilla2 PUBLIC
|
||||||
|
"${_QSCI_ROOT}/src"
|
||||||
|
)
|
||||||
|
target_include_directories(qscintilla2 PRIVATE
|
||||||
|
"${_QSCI_ROOT}/scintilla/include"
|
||||||
|
"${_QSCI_ROOT}/scintilla/lexlib"
|
||||||
|
"${_QSCI_ROOT}/scintilla/src"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(qscintilla2 PRIVATE
|
||||||
|
SCINTILLA_QT
|
||||||
|
SCI_LEXER
|
||||||
|
INCLUDE_DEPRECATED_FEATURES
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(qscintilla2 PUBLIC
|
||||||
|
${QT}::Widgets
|
||||||
|
${QT}::PrintSupport
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(qscintilla2 PROPERTIES AUTOMOC ON)
|
||||||
|
|
||||||
|
add_library(QScintilla::QScintilla ALIAS qscintilla2)
|
||||||
|
set(QScintilla_FOUND TRUE)
|
||||||
|
else()
|
||||||
|
set(QScintilla_FOUND FALSE)
|
||||||
|
if(QScintilla_FIND_REQUIRED)
|
||||||
|
message(FATAL_ERROR "Could NOT find QScintilla (missing source and pre-built library)")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
36
cmake/FindQt5.cmake
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Documentation: https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html#find-modules
|
||||||
|
|
||||||
|
# Always try config mode for the requested components (handles repeated calls)
|
||||||
|
find_package(Qt5 COMPONENTS ${Qt5_FIND_COMPONENTS} QUIET CONFIG)
|
||||||
|
|
||||||
|
if(Qt5_FOUND)
|
||||||
|
if(NOT Qt5_FIND_QUIETLY)
|
||||||
|
message(STATUS "Qt5 found: ${Qt5_DIR}")
|
||||||
|
endif()
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(Qt5_FIND_REQUIRED AND WIN32)
|
||||||
|
message(STATUS "Downloading Qt5...")
|
||||||
|
# Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP
|
||||||
|
if(POLICY CMP0135)
|
||||||
|
cmake_policy(SET CMP0135 NEW)
|
||||||
|
endif()
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_QUIET OFF)
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
FetchContent_Declare(Qt5
|
||||||
|
URL "https://github.com/x64dbg/deps/releases/download/2025.07.02/qt5.12.12-msvc2017_64.7z"
|
||||||
|
URL_HASH SHA256=770490bf09514982c8192ebde9a1fac8821108ba42b021f167bac54e85ada48a
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
FetchContent_Declare(Qt5
|
||||||
|
URL "https://github.com/x64dbg/deps/releases/download/2025.07.02/qt5.12.12-msvc2017.7z"
|
||||||
|
URL_HASH SHA256=3ff2a58e5ed772be475643cd7bb2df3e5499d7169d794ddf1ed5df5c5e862cb6
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
FetchContent_MakeAvailable(Qt5)
|
||||||
|
unset(FETCHCONTENT_QUIET)
|
||||||
|
set(Qt5_ROOT ${qt5_SOURCE_DIR})
|
||||||
|
find_package(Qt5 COMPONENTS ${Qt5_FIND_COMPONENTS} CONFIG REQUIRED)
|
||||||
|
endif()
|
||||||
82
cmake/deploy.cmake
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
|
if(CMAKE_SCRIPT_MODE_FILE)
|
||||||
|
set(TARGET_EXE ${CMAKE_ARGV3})
|
||||||
|
set(WINDEPLOYQT ${CMAKE_ARGV4})
|
||||||
|
get_filename_component(TARGET_DIR ${TARGET_EXE} DIRECTORY)
|
||||||
|
|
||||||
|
# Skip if already deployed for this build
|
||||||
|
if(EXISTS "${TARGET_DIR}/.qt_deployed")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Running windeployqt on ${TARGET_EXE}")
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${WINDEPLOYQT}
|
||||||
|
--pdb
|
||||||
|
--no-compiler-runtime
|
||||||
|
--no-translations
|
||||||
|
--no-opengl-sw
|
||||||
|
--no-system-d3d-compiler
|
||||||
|
--force
|
||||||
|
${TARGET_EXE}
|
||||||
|
RESULT_VARIABLE _result
|
||||||
|
)
|
||||||
|
|
||||||
|
if(_result EQUAL 0)
|
||||||
|
file(WRITE "${TARGET_DIR}/.qt_deployed" "")
|
||||||
|
message(STATUS "windeployqt completed successfully")
|
||||||
|
else()
|
||||||
|
message(WARNING "windeployqt failed with exit code ${_result}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ── Include mode: configure the deploy target ──
|
||||||
|
|
||||||
|
if(NOT WIN32)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Discover windeployqt from qmake
|
||||||
|
if(NOT TARGET ${QT}::windeployqt AND TARGET ${QT}::qmake)
|
||||||
|
get_target_property(_qt_qmake_location ${QT}::qmake IMPORTED_LOCATION)
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${_qt_qmake_location}" -query QT_INSTALL_PREFIX
|
||||||
|
RESULT_VARIABLE _return_code
|
||||||
|
OUTPUT_VARIABLE _qt_install_prefix
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
|
||||||
|
set(_windeployqt "${_qt_install_prefix}/bin/windeployqt.exe")
|
||||||
|
if(EXISTS ${_windeployqt})
|
||||||
|
add_executable(${QT}::windeployqt IMPORTED)
|
||||||
|
set_target_properties(${QT}::windeployqt PROPERTIES
|
||||||
|
IMPORTED_LOCATION ${_windeployqt}
|
||||||
|
)
|
||||||
|
message(STATUS "Found windeployqt: ${_windeployqt}")
|
||||||
|
else()
|
||||||
|
message(WARNING "windeployqt not found at ${_windeployqt}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(TARGET ${QT}::windeployqt)
|
||||||
|
add_custom_target(deploy
|
||||||
|
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
|
||||||
|
$<TARGET_FILE:Reclass>
|
||||||
|
$<TARGET_FILE:${QT}::windeployqt>
|
||||||
|
DEPENDS Reclass
|
||||||
|
COMMENT "Deploying Qt runtime DLLs..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Force re-deploy on rebuild
|
||||||
|
set_target_properties(deploy PROPERTIES
|
||||||
|
ADDITIONAL_CLEAN_FILES $<TARGET_FILE_DIR:Reclass>/.qt_deployed
|
||||||
|
)
|
||||||
|
endif()
|
||||||
29
cmake/raw_pdb_prefix.h
Normal 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
|
||||||
8
deploy/Reclass.desktop
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Reclass
|
||||||
|
Comment=Memory structure reverse engineering tool
|
||||||
|
Exec=Reclass
|
||||||
|
Icon=reclass
|
||||||
|
Categories=Development;Debugger;
|
||||||
|
Terminal=false
|
||||||
BIN
docs/README_PIC1.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
docs/README_PIC2.png
Normal file
|
After Width: | Height: | Size: 403 KiB |
BIN
docs/README_PIC3.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
160
docs/RECLASS_DARKMODE.svg
Normal 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
@@ -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 |
117
msvc/ProcessMemoryPlugin.vcxproj
Normal 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>
|
||||||
124
msvc/RcNetCompatPlugin.vcxproj
Normal 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
@@ -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>
|
||||||
202
msvc/Reclass.vcxproj
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<?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</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</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" />
|
||||||
|
<ClInclude Include="..\src\generator.h" />
|
||||||
|
<ClInclude Include="..\src\iplugin.h" />
|
||||||
|
<ClInclude Include="..\src\pluginmanager.h" />
|
||||||
|
<ClInclude Include="..\src\providerregistry.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" />
|
||||||
|
<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>
|
||||||
223
msvc/Reclass.vcxproj.filters
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<?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>
|
||||||
|
</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\main.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>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
118
msvc/RemoteProcessMemoryPlugin.vcxproj
Normal 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>
|
||||||
112
msvc/WinDbgMemoryPlugin.vcxproj
Normal 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
@@ -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 "$(FadecDir)parseinstrs.py" decode "$(FadecDir)instrs.txt" "$(FadecDir)fadec-decode-public.inc" "$(FadecDir)fadec-decode-private.inc" --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
@@ -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>
|
||||||
@@ -4,8 +4,7 @@ project(ProcessMemoryPlugin LANGUAGES CXX)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Find Qt
|
# Qt is found by the parent project; QT variable (Qt5 or Qt6) is inherited
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
@@ -24,7 +23,17 @@ set(PLUGIN_SOURCES
|
|||||||
add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||||
|
|
||||||
# Link Qt
|
# Link Qt
|
||||||
target_link_libraries(ProcessMemoryPlugin PRIVATE Qt6::Widgets)
|
target_link_libraries(ProcessMemoryPlugin PRIVATE ${QT}::Widgets ${_QT_WINEXTRAS})
|
||||||
|
|
||||||
|
# Platform-specific linking
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(ProcessMemoryPlugin PRIVATE psapi shell32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# On Linux, hide all symbols by default so only RCX_PLUGIN_EXPORT-marked ones are exported
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
target_compile_options(ProcessMemoryPlugin PRIVATE -fvisibility=hidden)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(ProcessMemoryPlugin PRIVATE
|
target_include_directories(ProcessMemoryPlugin PRIVATE
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
#include "ProcessMemoryPlugin.h"
|
#include "ProcessMemoryPlugin.h"
|
||||||
|
|
||||||
#include "../../src/processpicker.h"
|
#include "../../src/processpicker.h"
|
||||||
|
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(_WIN32)
|
||||||
|
#include <QtWin>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <climits>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
|
#endif
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// ProcessMemoryProvider implementation
|
// ProcessMemoryProvider implementation
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processName)
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||||
: m_handle(nullptr)
|
: m_handle(nullptr)
|
||||||
, m_pid(pid)
|
, m_pid(pid)
|
||||||
, m_processName(processName)
|
, m_processName(processName)
|
||||||
@@ -30,26 +56,24 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa
|
|||||||
m_writable = false;
|
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;
|
||||||
cacheModules();
|
cacheModules();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessMemoryProvider::~ProcessMemoryProvider()
|
|
||||||
{
|
|
||||||
if (m_handle)
|
|
||||||
CloseHandle(m_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||||
{
|
{
|
||||||
if (!m_handle || len <= 0) return false;
|
if (!m_handle || len <= 0) return false;
|
||||||
|
|
||||||
SIZE_T bytesRead = 0;
|
SIZE_T bytesRead = 0;
|
||||||
if (ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead))
|
ReadProcessMemory(m_handle, (LPCVOID)(addr), buf, (SIZE_T)len, &bytesRead);
|
||||||
return bytesRead == (SIZE_T)len;
|
if ((int)bytesRead < len)
|
||||||
return false;
|
memset((char*)buf + bytesRead, 0, len - bytesRead);
|
||||||
|
return bytesRead > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
@@ -57,16 +81,23 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
|||||||
if (!m_handle || !m_writable || len <= 0) return false;
|
if (!m_handle || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
SIZE_T bytesWritten = 0;
|
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 bytesWritten == (SIZE_T)len;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
{
|
{
|
||||||
// TODO: Implement module enumeration with EnumProcessModules
|
for (const auto& mod : m_modules)
|
||||||
// For now, just return empty (no symbol resolution)
|
{
|
||||||
Q_UNUSED(addr);
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +129,303 @@ void ProcessMemoryProvider::cacheModules()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
: m_fd(-1)
|
||||||
|
, m_pid(pid)
|
||||||
|
, m_processName(processName)
|
||||||
|
, m_writable(false)
|
||||||
|
, m_base(0)
|
||||||
|
{
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
QByteArray pathUtf8 = memPath.toUtf8();
|
||||||
|
|
||||||
|
// Try read-write first
|
||||||
|
m_fd = ::open(pathUtf8.constData(), O_RDWR);
|
||||||
|
if (m_fd >= 0)
|
||||||
|
m_writable = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fall back to read-only
|
||||||
|
m_fd = ::open(pathUtf8.constData(), O_RDONLY);
|
||||||
|
m_writable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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*>(addr);
|
||||||
|
remote.iov_len = static_cast<size_t>(len);
|
||||||
|
|
||||||
|
ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0);
|
||||||
|
if (nread == static_cast<ssize_t>(len))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Fallback: pread on /proc/<pid>/mem
|
||||||
|
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
|
||||||
|
return nread == static_cast<ssize_t>(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
|
{
|
||||||
|
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
|
// 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*>(addr);
|
||||||
|
remote.iov_len = static_cast<size_t>(len);
|
||||||
|
|
||||||
|
ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0);
|
||||||
|
if (nwritten == static_cast<ssize_t>(len))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Fallback: pwrite on /proc/<pid>/mem
|
||||||
|
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
|
||||||
|
return nwritten == static_cast<ssize_t>(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProcessMemoryProvider::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 {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessMemoryProvider::cacheModules()
|
||||||
|
{
|
||||||
|
// Parse /proc/<pid>/maps to discover loaded modules
|
||||||
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
||||||
|
std::ifstream mapsFile(mapsPath.toStdString());
|
||||||
|
if (!mapsFile.is_open()) return;
|
||||||
|
|
||||||
|
// Accumulate base/end per path, then convert to ModuleInfo
|
||||||
|
struct Range { uint64_t base; uint64_t end; };
|
||||||
|
QMap<QString, Range> moduleRanges;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
bool firstExec = true;
|
||||||
|
while (std::getline(mapsFile, line))
|
||||||
|
{
|
||||||
|
// Format: addr_start-addr_end perms offset dev inode pathname
|
||||||
|
// Example: 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/foo
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string addrRange, perms, offset, dev, inode, pathname;
|
||||||
|
iss >> addrRange >> perms >> offset >> dev >> inode;
|
||||||
|
std::getline(iss, pathname);
|
||||||
|
|
||||||
|
// Trim leading whitespace from pathname
|
||||||
|
size_t start = pathname.find_first_not_of(" \t");
|
||||||
|
if (start == std::string::npos) continue;
|
||||||
|
pathname = pathname.substr(start);
|
||||||
|
|
||||||
|
// Skip non-file mappings
|
||||||
|
if (pathname.empty() || pathname[0] != '/') continue;
|
||||||
|
// Skip special mappings
|
||||||
|
if (pathname.find("/dev/") == 0 || pathname.find("/memfd:") == 0) continue;
|
||||||
|
|
||||||
|
// Parse address range
|
||||||
|
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);
|
||||||
|
|
||||||
|
QString qpath = QString::fromStdString(pathname);
|
||||||
|
|
||||||
|
// Track first executable mapping as the base address
|
||||||
|
if (firstExec && perms.size() >= 3 && perms[2] == 'x')
|
||||||
|
{
|
||||||
|
m_base = addrStart;
|
||||||
|
firstExec = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = moduleRanges.find(qpath);
|
||||||
|
if (it != moduleRanges.end())
|
||||||
|
{
|
||||||
|
if (addrStart < it->base) it->base = addrStart;
|
||||||
|
if (addrEnd > it->end) it->end = addrEnd;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moduleRanges.insert(qpath, {addrStart, addrEnd});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_modules.reserve(moduleRanges.size());
|
||||||
|
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
||||||
|
{
|
||||||
|
QFileInfo fi(it.key());
|
||||||
|
m_modules.append({
|
||||||
|
fi.fileName(),
|
||||||
|
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
|
||||||
|
if (m_handle)
|
||||||
|
CloseHandle(m_handle);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
if (m_fd >= 0)
|
||||||
|
::close(m_fd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProcessMemoryProvider::size() const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return m_handle ? 0x10000 : 0;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return (m_fd >= 0) ? 0x10000 : 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// ProcessMemoryPlugin implementation
|
// ProcessMemoryPlugin implementation
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
@@ -119,9 +447,10 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
|||||||
// Parse target: "pid:name" or just "pid"
|
// Parse target: "pid:name" or just "pid"
|
||||||
QStringList parts = target.split(':');
|
QStringList parts = target.split(':');
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
DWORD pid = parts[0].toUInt(&ok);
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
|
||||||
if (!ok || pid == 0) {
|
if (!ok || pid == 0)
|
||||||
|
{
|
||||||
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -132,11 +461,9 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
|||||||
if (!provider->isValid())
|
if (!provider->isValid())
|
||||||
{
|
{
|
||||||
if (errorMsg)
|
if (errorMsg)
|
||||||
{
|
|
||||||
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
||||||
"Ensure the process is running and you have sufficient permissions.")
|
"Ensure the process is running and you have sufficient permissions.")
|
||||||
.arg(name).arg(pid);
|
.arg(name).arg(pid);
|
||||||
}
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,13 +491,36 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
|||||||
{
|
{
|
||||||
MODULEINFO mi{};
|
MODULEINFO mi{};
|
||||||
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
||||||
{
|
|
||||||
base = (uint64_t)mi.lpBaseOfDll;
|
base = (uint64_t)mi.lpBaseOfDll;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(hProc);
|
CloseHandle(hProc);
|
||||||
return base;
|
return base;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
// Parse PID from target
|
||||||
|
QStringList parts = target.split(':');
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) return 0;
|
||||||
|
|
||||||
|
// Find first executable mapping from /proc/<pid>/maps
|
||||||
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(pid);
|
||||||
|
std::ifstream mapsFile(mapsPath.toStdString());
|
||||||
|
if (!mapsFile.is_open()) return 0;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(mapsFile, line)) {
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string addrRange, perms;
|
||||||
|
iss >> addrRange >> perms;
|
||||||
|
if (perms.size() >= 3 && perms[2] == 'x') {
|
||||||
|
auto dash = addrRange.find('-');
|
||||||
|
if (dash != std::string::npos) {
|
||||||
|
return std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
#else
|
#else
|
||||||
Q_UNUSED(target);
|
Q_UNUSED(target);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -191,6 +541,7 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
info.name = pinfo.name;
|
info.name = pinfo.name;
|
||||||
info.path = pinfo.path;
|
info.path = pinfo.path;
|
||||||
info.icon = pinfo.icon;
|
info.icon = pinfo.icon;
|
||||||
|
info.is32Bit = pinfo.is32Bit;
|
||||||
processes.append(info);
|
processes.append(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,13 +592,22 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|||||||
SHFILEINFOW sfi = {};
|
SHFILEINFOW sfi = {};
|
||||||
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
||||||
if (sfi.hIcon) {
|
if (sfi.hIcon) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
||||||
|
#else
|
||||||
|
QPixmap pixmap = QtWin::fromHICON(sfi.hIcon);
|
||||||
|
#endif
|
||||||
info.icon = QIcon(pixmap);
|
info.icon = QIcon(pixmap);
|
||||||
DestroyIcon(sfi.hIcon);
|
DestroyIcon(sfi.hIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect 32-bit (WoW64) process
|
||||||
|
BOOL isWow64 = FALSE;
|
||||||
|
if (IsWow64Process(hProcess, &isWow64) && isWow64)
|
||||||
|
info.is32Bit = true;
|
||||||
|
|
||||||
CloseHandle(hProcess);
|
CloseHandle(hProcess);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +617,55 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
QDir procDir("/proc");
|
||||||
|
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
|
|
||||||
|
for (const QString& entry : entries) {
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = entry.toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) continue;
|
||||||
|
|
||||||
|
// Read process name from /proc/<pid>/comm
|
||||||
|
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||||
|
QFile commFile(commPath);
|
||||||
|
QString procName;
|
||||||
|
if (commFile.open(QIODevice::ReadOnly)) {
|
||||||
|
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||||
|
commFile.close();
|
||||||
|
}
|
||||||
|
if (procName.isEmpty()) continue; // Skip kernel threads with no name
|
||||||
|
|
||||||
|
// Read exe path from /proc/<pid>/exe symlink
|
||||||
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||||
|
QFileInfo exeInfo(exePath);
|
||||||
|
QString resolvedPath;
|
||||||
|
if (exeInfo.exists())
|
||||||
|
resolvedPath = exeInfo.symLinkTarget();
|
||||||
|
|
||||||
|
// Skip if we can't read the process memory (no access)
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PluginProcessInfo info;
|
||||||
|
info.pid = pid;
|
||||||
|
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
|
#endif
|
||||||
|
|
||||||
return processes;
|
return processes;
|
||||||
@@ -266,7 +675,7 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|||||||
// Plugin factory
|
// Plugin factory
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) IPlugin* CreatePlugin()
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||||
{
|
{
|
||||||
return new ProcessMemoryPlugin();
|
return new ProcessMemoryPlugin();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../../src/iplugin.h"
|
#include "../../src/iplugin.h"
|
||||||
#include "../../src/core.h"
|
#include "../../src/core.h"
|
||||||
#include <windows.h>
|
|
||||||
#include <tlhelp32.h>
|
#include <cstdint>
|
||||||
#include <psapi.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Windows process memory provider
|
* Process memory provider
|
||||||
* Reads/writes memory from a live process using Win32 API
|
* Reads/writes memory from a live process using platform APIs
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryProvider : public rcx::Provider {
|
class ProcessMemoryProvider : public rcx::Provider
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ProcessMemoryProvider(DWORD pid, const QString& processName);
|
ProcessMemoryProvider(uint32_t pid, const QString& processName);
|
||||||
~ProcessMemoryProvider() override;
|
~ProcessMemoryProvider() override;
|
||||||
|
|
||||||
// Required overrides
|
// Required overrides
|
||||||
bool read(uint64_t addr, void* buf, int len) const override;
|
bool read(uint64_t addr, void* buf, int len) const override;
|
||||||
int size() const override { return m_handle ? INT_MAX : NULL; } // Process memory has no fixed size
|
int size() const override;
|
||||||
|
|
||||||
// Optional overrides
|
// Optional overrides
|
||||||
bool write(uint64_t addr, const void* buf, int len) override;
|
bool write(uint64_t addr, const void* buf, int len) override;
|
||||||
@@ -25,21 +24,38 @@ public:
|
|||||||
QString name() const override { return m_processName; }
|
QString name() const override { return m_processName; }
|
||||||
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
||||||
QString getSymbol(uint64_t addr) const override;
|
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 {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return m_handle && len >= 0;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return m_fd >= 0 && len >= 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Process-specific helpers
|
// Process-specific helpers
|
||||||
DWORD pid() const { return m_pid; }
|
uint32_t pid() const { return m_pid; }
|
||||||
uint64_t baseAddress() const { return m_base; }
|
|
||||||
void refreshModules() { m_modules.clear(); cacheModules(); }
|
void refreshModules() { m_modules.clear(); cacheModules(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cacheModules();
|
void cacheModules();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HANDLE m_handle;
|
#ifdef _WIN32
|
||||||
DWORD m_pid;
|
void* m_handle;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
int m_fd;
|
||||||
|
#endif
|
||||||
|
uint32_t m_pid;
|
||||||
QString m_processName;
|
QString m_processName;
|
||||||
bool m_writable;
|
bool m_writable;
|
||||||
uint64_t m_base;
|
uint64_t m_base;
|
||||||
|
int m_pointerSize = 8;
|
||||||
|
|
||||||
struct ModuleInfo {
|
struct ModuleInfo {
|
||||||
QString name;
|
QString name;
|
||||||
@@ -52,12 +68,13 @@ private:
|
|||||||
/**
|
/**
|
||||||
* Plugin that provides ProcessMemoryProvider
|
* Plugin that provides ProcessMemoryProvider
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryPlugin : public IProviderPlugin {
|
class ProcessMemoryPlugin : public IProviderPlugin
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
std::string Name() const override { return "Process Memory"; }
|
std::string Name() const override { return "Process Memory"; }
|
||||||
std::string Version() const override { return "1.0.0"; }
|
std::string Version() const override { return "1.0.0"; }
|
||||||
std::string Author() const override { return "ReclassX"; }
|
std::string Author() const override { return "Reclass"; }
|
||||||
std::string Description() const override { return "Read and write memory from local running Windows processes"; }
|
std::string Description() const override { return "Read and write memory from local running processes"; }
|
||||||
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||||
QIcon Icon() const override;
|
QIcon Icon() const override;
|
||||||
|
|
||||||
@@ -72,4 +89,4 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Plugin export
|
// Plugin export
|
||||||
extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||||
|
|||||||
93
plugins/RcNetPluginCompatLayer/CMakeLists.txt
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(RcNetCompatPlugin 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 ON)
|
||||||
|
|
||||||
|
# Plugin sources
|
||||||
|
set(PLUGIN_SOURCES
|
||||||
|
RcNetCompatPlugin.h
|
||||||
|
RcNetCompatPlugin.cpp
|
||||||
|
RcNetCompatProvider.h
|
||||||
|
RcNetCompatProvider.cpp
|
||||||
|
ReClassNET_Plugin.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- Optional .NET bridge -------------------------------------------------
|
||||||
|
# When the .NET SDK is available, build the C# bridge assembly and enable
|
||||||
|
# CLR hosting support in the C++ plugin.
|
||||||
|
|
||||||
|
find_program(DOTNET_EXE dotnet)
|
||||||
|
if(DOTNET_EXE)
|
||||||
|
# Check that 'dotnet build' actually works for net472
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${DOTNET_EXE} --list-sdks
|
||||||
|
OUTPUT_VARIABLE _dotnet_sdks
|
||||||
|
ERROR_QUIET
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
if(_dotnet_sdks)
|
||||||
|
set(HAS_CLR_BRIDGE ON)
|
||||||
|
message(STATUS "RcNetCompat: .NET SDK found -- building managed bridge")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(HAS_CLR_BRIDGE)
|
||||||
|
list(APPEND PLUGIN_SOURCES
|
||||||
|
ClrHost.h
|
||||||
|
ClrHost.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the C# bridge assembly
|
||||||
|
set(_bridge_src "${CMAKE_CURRENT_SOURCE_DIR}/bridge")
|
||||||
|
set(_bridge_out "${CMAKE_BINARY_DIR}/Plugins/RcNetBridge.dll")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${_bridge_out}"
|
||||||
|
COMMAND ${DOTNET_EXE} build
|
||||||
|
"${_bridge_src}/RcNetBridge.csproj"
|
||||||
|
-c Release
|
||||||
|
-o "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
--nologo -v quiet
|
||||||
|
DEPENDS
|
||||||
|
"${_bridge_src}/RcNetBridge.cs"
|
||||||
|
"${_bridge_src}/RcNetBridge.csproj"
|
||||||
|
COMMENT "Building RcNetBridge.dll (.NET bridge)..."
|
||||||
|
)
|
||||||
|
add_custom_target(RcNetBridge ALL DEPENDS "${_bridge_out}")
|
||||||
|
else()
|
||||||
|
message(STATUS "RcNetCompat: .NET SDK not found -- managed plugin support disabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Create shared library (DLL)
|
||||||
|
add_library(RcNetCompatPlugin SHARED ${PLUGIN_SOURCES})
|
||||||
|
|
||||||
|
if(HAS_CLR_BRIDGE)
|
||||||
|
target_compile_definitions(RcNetCompatPlugin PRIVATE HAS_CLR_BRIDGE=1)
|
||||||
|
add_dependencies(RcNetCompatPlugin RcNetBridge)
|
||||||
|
# CLR hosting uses COM (ole32)
|
||||||
|
target_link_libraries(RcNetCompatPlugin PRIVATE ole32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link Qt
|
||||||
|
target_link_libraries(RcNetCompatPlugin PRIVATE ${QT}::Widgets ${_QT_WINEXTRAS})
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
target_include_directories(RcNetCompatPlugin PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output to Plugins folder
|
||||||
|
set_target_properties(RcNetCompatPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
)
|
||||||
162
plugins/RcNetPluginCompatLayer/ClrHost.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#include "ClrHost.h"
|
||||||
|
|
||||||
|
#include <cwchar>
|
||||||
|
|
||||||
|
// -- GUIDs ----------------------------------------------------------------
|
||||||
|
|
||||||
|
using FnCLRCreateInstance = HRESULT(STDAPICALLTYPE*)(REFCLSID, REFIID, LPVOID*);
|
||||||
|
|
||||||
|
// {9280188D-0E8E-4867-B30C-7FA83884E8DE}
|
||||||
|
static const GUID sCLSID_CLRMetaHost =
|
||||||
|
{0x9280188d, 0x0e8e, 0x4867, {0xb3, 0x0c, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde}};
|
||||||
|
|
||||||
|
// {D332DB9E-B9B3-4125-8207-A14884F53216}
|
||||||
|
static const GUID sIID_ICLRMetaHost =
|
||||||
|
{0xD332DB9E, 0xB9B3, 0x4125, {0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16}};
|
||||||
|
|
||||||
|
// {BD39D1D2-BA2F-486A-89B0-B4B0CB466891}
|
||||||
|
static const GUID sIID_ICLRRuntimeInfo =
|
||||||
|
{0xBD39D1D2, 0xBA2F, 0x486a, {0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91}};
|
||||||
|
|
||||||
|
// {90F1A06E-7712-4762-86B5-7A5EBA6BDB02}
|
||||||
|
static const GUID sCLSID_CLRRuntimeHost =
|
||||||
|
{0x90F1A06E, 0x7712, 0x4762, {0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02}};
|
||||||
|
|
||||||
|
// {90F1A06C-7712-4762-86B5-7A5EBA6BDB02}
|
||||||
|
static const GUID sIID_ICLRRuntimeHost =
|
||||||
|
{0x90F1A06C, 0x7712, 0x4762, {0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02}};
|
||||||
|
|
||||||
|
// -- ClrHost implementation -----------------------------------------------
|
||||||
|
|
||||||
|
ClrHost::ClrHost()
|
||||||
|
{
|
||||||
|
startClr();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClrHost::~ClrHost()
|
||||||
|
{
|
||||||
|
if (m_runtimeHost) m_runtimeHost->Release();
|
||||||
|
if (m_runtimeInfo) m_runtimeInfo->Release();
|
||||||
|
if (m_metaHost) m_metaHost->Release();
|
||||||
|
if (m_mscoree) FreeLibrary(m_mscoree);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClrHost::startClr()
|
||||||
|
{
|
||||||
|
m_mscoree = LoadLibraryW(L"mscoree.dll");
|
||||||
|
if (!m_mscoree)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto fnCreate = reinterpret_cast<FnCLRCreateInstance>(
|
||||||
|
GetProcAddress(m_mscoree, "CLRCreateInstance"));
|
||||||
|
if (!fnCreate)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
HRESULT hr = fnCreate(sCLSID_CLRMetaHost, sIID_ICLRMetaHost,
|
||||||
|
reinterpret_cast<LPVOID*>(&m_metaHost));
|
||||||
|
if (FAILED(hr) || !m_metaHost)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hr = m_metaHost->GetRuntime(L"v4.0.30319", sIID_ICLRRuntimeInfo,
|
||||||
|
reinterpret_cast<LPVOID*>(&m_runtimeInfo));
|
||||||
|
if (FAILED(hr) || !m_runtimeInfo)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hr = m_runtimeInfo->GetInterface(sCLSID_CLRRuntimeHost, sIID_ICLRRuntimeHost,
|
||||||
|
(LPVOID*)&m_runtimeHost);
|
||||||
|
if (FAILED(hr) || !m_runtimeHost)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hr = m_runtimeHost->Start();
|
||||||
|
if (FAILED(hr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_clrStarted = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClrHost::loadManagedPlugin(const QString& bridgeDllPath,
|
||||||
|
const QString& pluginPath,
|
||||||
|
RcNetFunctions* outFunctions,
|
||||||
|
QString* errorMsg)
|
||||||
|
{
|
||||||
|
if (!m_runtimeHost || !m_clrStarted) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
".NET Framework 4.x is not available on this machine.\n"
|
||||||
|
"Install the .NET Framework 4.7.2+ runtime to load managed plugins.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Zero the function table -- the bridge will fill it
|
||||||
|
memset(outFunctions, 0, sizeof(RcNetFunctions));
|
||||||
|
|
||||||
|
// Build the argument string: "<hex_address_of_function_table>|<plugin_path>"
|
||||||
|
// Use %ls (not %s) for wide strings -- MinGW follows POSIX conventions.
|
||||||
|
wchar_t arg[2048];
|
||||||
|
swprintf(arg, sizeof(arg) / sizeof(wchar_t),
|
||||||
|
L"%llx|%ls",
|
||||||
|
reinterpret_cast<unsigned long long>(outFunctions),
|
||||||
|
reinterpret_cast<const wchar_t*>(pluginPath.utf16()));
|
||||||
|
|
||||||
|
DWORD retVal = 0;
|
||||||
|
HRESULT hr = m_runtimeHost->ExecuteInDefaultAppDomain(
|
||||||
|
reinterpret_cast<LPCWSTR>(bridgeDllPath.utf16()),
|
||||||
|
L"RcNetBridge.Bridge",
|
||||||
|
L"Initialize",
|
||||||
|
arg,
|
||||||
|
&retVal
|
||||||
|
);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"Failed to execute .NET bridge (HRESULT 0x%1).\n"
|
||||||
|
"Bridge: %2\n"
|
||||||
|
"Plugin: %3")
|
||||||
|
.arg(static_cast<uint>(hr), 8, 16, QChar('0'))
|
||||||
|
.arg(bridgeDllPath)
|
||||||
|
.arg(pluginPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retVal != 0) {
|
||||||
|
if (errorMsg) {
|
||||||
|
switch (retVal) {
|
||||||
|
case 1:
|
||||||
|
*errorMsg = QStringLiteral("Bridge: invalid argument format.");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"No ICoreProcessFunctions implementation found in the .NET plugin.\n"
|
||||||
|
"The DLL may not be a ReClass.NET plugin.");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"Failed to load the .NET plugin assembly.\n"
|
||||||
|
"Check that all its dependencies are available.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*errorMsg = QStringLiteral("Bridge returned error code %1.").arg(retVal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the bridge wrote at least the minimum required function pointers
|
||||||
|
if (!outFunctions->ReadRemoteMemory ||
|
||||||
|
!outFunctions->OpenRemoteProcess ||
|
||||||
|
!outFunctions->EnumerateProcesses ||
|
||||||
|
!outFunctions->CloseRemoteProcess) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"The .NET bridge loaded but did not provide the required functions "
|
||||||
|
"(ReadRemoteMemory, OpenRemoteProcess, CloseRemoteProcess, EnumerateProcesses).");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
99
plugins/RcNetPluginCompatLayer/ClrHost.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#pragma once
|
||||||
|
// In-process CLR hosting for loading .NET ReClass.NET plugins.
|
||||||
|
// Dynamically loads mscoree.dll and uses ICLRMetaHost -> ICLRRuntimeInfo ->
|
||||||
|
// ICLRRuntimeHost::ExecuteInDefaultAppDomain to call into the C# bridge.
|
||||||
|
|
||||||
|
#include "ReClassNET_Plugin.hpp"
|
||||||
|
#include <QString>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
// -- Minimal COM interface definitions for CLR hosting --------------------
|
||||||
|
// Defined here to avoid depending on Windows SDK metahost.h / mscoree.h
|
||||||
|
// which may not be present in all MinGW distributions.
|
||||||
|
// Only methods we actually call have real signatures; the rest are stubs
|
||||||
|
// that preserve correct vtable offsets.
|
||||||
|
|
||||||
|
#undef INTERFACE
|
||||||
|
#define INTERFACE ICLRMetaHost
|
||||||
|
DECLARE_INTERFACE_(ICLRMetaHost, IUnknown)
|
||||||
|
{
|
||||||
|
// IUnknown
|
||||||
|
STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;
|
||||||
|
STDMETHOD_(ULONG, AddRef)() PURE;
|
||||||
|
STDMETHOD_(ULONG, Release)() PURE;
|
||||||
|
// ICLRMetaHost
|
||||||
|
STDMETHOD(GetRuntime)(LPCWSTR pwzVersion, REFIID riid, LPVOID* ppRuntime) PURE;
|
||||||
|
STDMETHOD(GetVersionFromFile)(LPCWSTR, LPWSTR, DWORD*) PURE;
|
||||||
|
STDMETHOD(EnumerateInstalledRuntimes)(void**) PURE;
|
||||||
|
STDMETHOD(EnumerateLoadedRuntimes)(HANDLE, void**) PURE;
|
||||||
|
STDMETHOD(RequestRuntimeLoadedNotification)(void*) PURE;
|
||||||
|
STDMETHOD(QueryLegacyV2RuntimeBinding)(REFIID, LPVOID*) PURE;
|
||||||
|
STDMETHOD_(void, ExitProcess)(INT32) PURE;
|
||||||
|
};
|
||||||
|
#undef INTERFACE
|
||||||
|
|
||||||
|
#define INTERFACE ICLRRuntimeInfo
|
||||||
|
DECLARE_INTERFACE_(ICLRRuntimeInfo, IUnknown)
|
||||||
|
{
|
||||||
|
// IUnknown
|
||||||
|
STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;
|
||||||
|
STDMETHOD_(ULONG, AddRef)() PURE;
|
||||||
|
STDMETHOD_(ULONG, Release)() PURE;
|
||||||
|
// ICLRRuntimeInfo
|
||||||
|
STDMETHOD(GetVersionString)(LPWSTR, DWORD*) PURE;
|
||||||
|
STDMETHOD(GetRuntimeDirectory)(LPWSTR, DWORD*) PURE;
|
||||||
|
STDMETHOD(IsLoaded)(HANDLE, BOOL*) PURE;
|
||||||
|
STDMETHOD(LoadErrorString)(UINT, LPWSTR, DWORD*, LONG) PURE;
|
||||||
|
STDMETHOD(LoadLibrary)(LPCWSTR, HMODULE*) PURE;
|
||||||
|
STDMETHOD(GetProcAddress)(LPCSTR, LPVOID*) PURE;
|
||||||
|
STDMETHOD(GetInterface)(REFCLSID rclsid, REFIID riid, LPVOID* ppUnk) PURE;
|
||||||
|
};
|
||||||
|
#undef INTERFACE
|
||||||
|
|
||||||
|
#define INTERFACE ICLRRuntimeHost
|
||||||
|
DECLARE_INTERFACE_(ICLRRuntimeHost, IUnknown)
|
||||||
|
{
|
||||||
|
// IUnknown
|
||||||
|
STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;
|
||||||
|
STDMETHOD_(ULONG, AddRef)() PURE;
|
||||||
|
STDMETHOD_(ULONG, Release)() PURE;
|
||||||
|
// ICLRRuntimeHost
|
||||||
|
STDMETHOD(Start)() PURE;
|
||||||
|
STDMETHOD(Stop)() PURE;
|
||||||
|
STDMETHOD(SetHostControl)(void*) PURE;
|
||||||
|
STDMETHOD(GetCLRControl)(void**) PURE;
|
||||||
|
STDMETHOD(UnloadAppDomain)(DWORD, BOOL) PURE;
|
||||||
|
STDMETHOD(ExecuteInAppDomain)(DWORD, void*, void*) PURE;
|
||||||
|
STDMETHOD(GetCurrentAppDomainId)(DWORD*) PURE;
|
||||||
|
STDMETHOD(ExecuteApplication)(LPCWSTR, DWORD, LPCWSTR*, DWORD, LPCWSTR*, int*) PURE;
|
||||||
|
STDMETHOD(ExecuteInDefaultAppDomain)(LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, DWORD*) PURE;
|
||||||
|
};
|
||||||
|
#undef INTERFACE
|
||||||
|
|
||||||
|
// -- CLR Host wrapper -----------------------------------------------------
|
||||||
|
|
||||||
|
class ClrHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClrHost();
|
||||||
|
~ClrHost();
|
||||||
|
|
||||||
|
// True if the .NET Framework CLR (v4.0) is available on this machine.
|
||||||
|
bool isAvailable() const { return m_runtimeHost != nullptr && m_clrStarted; }
|
||||||
|
|
||||||
|
// Load a managed ReClass.NET plugin via the C# bridge.
|
||||||
|
bool loadManagedPlugin(const QString& bridgeDllPath,
|
||||||
|
const QString& pluginPath,
|
||||||
|
RcNetFunctions* outFunctions,
|
||||||
|
QString* errorMsg = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool startClr();
|
||||||
|
|
||||||
|
HMODULE m_mscoree = nullptr;
|
||||||
|
ICLRMetaHost* m_metaHost = nullptr;
|
||||||
|
ICLRRuntimeInfo* m_runtimeInfo = nullptr;
|
||||||
|
ICLRRuntimeHost* m_runtimeHost = nullptr;
|
||||||
|
bool m_clrStarted = false;
|
||||||
|
};
|
||||||
333
plugins/RcNetPluginCompatLayer/RcNetCompatPlugin.cpp
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
#include "RcNetCompatPlugin.h"
|
||||||
|
#include "RcNetCompatProvider.h"
|
||||||
|
#include "../../src/processpicker.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QStyle>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// -- Helpers --------------------------------------------------------------
|
||||||
|
|
||||||
|
QIcon RcNetCompatPlugin::Icon() const
|
||||||
|
{
|
||||||
|
return qApp->style()->standardIcon(QStyle::SP_TrashIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --.NET assembly detection ----------------------------------------------
|
||||||
|
|
||||||
|
static bool isDotNetAssembly(const QString& path)
|
||||||
|
{
|
||||||
|
// A .NET assembly has a non-zero CLR header directory entry in the PE
|
||||||
|
// optional header. We check this by loading the PE without running
|
||||||
|
// DllMain and inspecting the IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR.
|
||||||
|
HMODULE hMod = GetModuleHandleW(reinterpret_cast<LPCWSTR>(path.utf16()));
|
||||||
|
if (!hMod)
|
||||||
|
hMod = LoadLibraryExW(reinterpret_cast<LPCWSTR>(path.utf16()),
|
||||||
|
nullptr, DONT_RESOLVE_DLL_REFERENCES);
|
||||||
|
if (!hMod) return false;
|
||||||
|
|
||||||
|
auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(hMod);
|
||||||
|
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false;
|
||||||
|
|
||||||
|
auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(
|
||||||
|
reinterpret_cast<const char*>(hMod) + dos->e_lfanew);
|
||||||
|
if (nt->Signature != IMAGE_NT_SIGNATURE) return false;
|
||||||
|
|
||||||
|
constexpr DWORD kClrIndex = IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR; // 14
|
||||||
|
DWORD rva = 0, dirSize = 0;
|
||||||
|
|
||||||
|
if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||||||
|
auto* opt = reinterpret_cast<const IMAGE_OPTIONAL_HEADER64*>(&nt->OptionalHeader);
|
||||||
|
if (opt->NumberOfRvaAndSizes > kClrIndex) {
|
||||||
|
rva = opt->DataDirectory[kClrIndex].VirtualAddress;
|
||||||
|
dirSize = opt->DataDirectory[kClrIndex].Size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto* opt = reinterpret_cast<const IMAGE_OPTIONAL_HEADER32*>(&nt->OptionalHeader);
|
||||||
|
if (opt->NumberOfRvaAndSizes > kClrIndex) {
|
||||||
|
rva = opt->DataDirectory[kClrIndex].VirtualAddress;
|
||||||
|
dirSize = opt->DataDirectory[kClrIndex].Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rva != 0 && dirSize != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --Unified loader (dispatches native vs managed) ------------------------
|
||||||
|
|
||||||
|
bool RcNetCompatPlugin::loadPlugin(const QString& path, QString* errorMsg)
|
||||||
|
{
|
||||||
|
if (m_dllPath == path && (m_lib || m_isManaged))
|
||||||
|
return true; // Already loaded
|
||||||
|
|
||||||
|
if (isDotNetAssembly(path)) {
|
||||||
|
#ifdef HAS_CLR_BRIDGE
|
||||||
|
return loadManagedDll(path, errorMsg);
|
||||||
|
#else
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"This is a .NET assembly.\n\n"
|
||||||
|
"This build does not include .NET bridge support.\n"
|
||||||
|
"Rebuild with the .NET SDK installed to enable managed plugin loading.");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return loadNativeDll(path, errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --Native DLL loading ---------------------------------------------------
|
||||||
|
|
||||||
|
bool RcNetCompatPlugin::loadNativeDll(const QString& path, QString* errorMsg)
|
||||||
|
{
|
||||||
|
unloadNativeDll();
|
||||||
|
|
||||||
|
m_lib = std::make_unique<QLibrary>(path);
|
||||||
|
if (!m_lib->load()) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral("Failed to load DLL: %1").arg(m_lib->errorString());
|
||||||
|
m_lib.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve all function pointers
|
||||||
|
m_fns.EnumerateProcesses =
|
||||||
|
reinterpret_cast<FnEnumerateProcesses>(m_lib->resolve("EnumerateProcesses"));
|
||||||
|
m_fns.OpenRemoteProcess =
|
||||||
|
reinterpret_cast<FnOpenRemoteProcess>(m_lib->resolve("OpenRemoteProcess"));
|
||||||
|
m_fns.IsProcessValid =
|
||||||
|
reinterpret_cast<FnIsProcessValid>(m_lib->resolve("IsProcessValid"));
|
||||||
|
m_fns.CloseRemoteProcess =
|
||||||
|
reinterpret_cast<FnCloseRemoteProcess>(m_lib->resolve("CloseRemoteProcess"));
|
||||||
|
m_fns.ReadRemoteMemory =
|
||||||
|
reinterpret_cast<FnReadRemoteMemory>(m_lib->resolve("ReadRemoteMemory"));
|
||||||
|
m_fns.WriteRemoteMemory =
|
||||||
|
reinterpret_cast<FnWriteRemoteMemory>(m_lib->resolve("WriteRemoteMemory"));
|
||||||
|
m_fns.EnumerateRemoteSectionsAndModules =
|
||||||
|
reinterpret_cast<FnEnumerateRemoteSectionsAndModules>(
|
||||||
|
m_lib->resolve("EnumerateRemoteSectionsAndModules"));
|
||||||
|
m_fns.ControlRemoteProcess =
|
||||||
|
reinterpret_cast<FnControlRemoteProcess>(m_lib->resolve("ControlRemoteProcess"));
|
||||||
|
|
||||||
|
// At minimum we need read + open + close
|
||||||
|
if (!m_fns.ReadRemoteMemory || !m_fns.OpenRemoteProcess || !m_fns.CloseRemoteProcess || !m_fns.EnumerateProcesses) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"DLL is missing required exports (ReadRemoteMemory, OpenRemoteProcess, "
|
||||||
|
"CloseRemoteProcess, EnumerateProcesses). Is this a ReClass.NET native plugin?");
|
||||||
|
m_lib->unload();
|
||||||
|
m_lib.reset();
|
||||||
|
m_fns = {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dllPath = path;
|
||||||
|
m_isManaged = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RcNetCompatPlugin::unloadNativeDll()
|
||||||
|
{
|
||||||
|
if (m_lib) {
|
||||||
|
m_lib->unload();
|
||||||
|
m_lib.reset();
|
||||||
|
}
|
||||||
|
m_fns = {};
|
||||||
|
m_dllPath.clear();
|
||||||
|
m_isManaged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --Managed (.NET) DLL loading via CLR bridge ----------------------------
|
||||||
|
|
||||||
|
#ifdef HAS_CLR_BRIDGE
|
||||||
|
|
||||||
|
bool RcNetCompatPlugin::loadManagedDll(const QString& path, QString* errorMsg)
|
||||||
|
{
|
||||||
|
unloadNativeDll();
|
||||||
|
|
||||||
|
// Lazily create the CLR host (one per plugin lifetime)
|
||||||
|
if (!m_clrHost)
|
||||||
|
m_clrHost = std::make_unique<ClrHost>();
|
||||||
|
|
||||||
|
if (!m_clrHost->isAvailable()) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
".NET Framework 4.x is not available on this machine.\n"
|
||||||
|
"Install the .NET Framework 4.7.2+ runtime to load managed plugins.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate RcNetBridge.dll next to our own plugin DLL
|
||||||
|
// Use native separators -- the CLR expects Windows-style backslash paths.
|
||||||
|
QString bridgePath = QDir::toNativeSeparators(
|
||||||
|
QCoreApplication::applicationDirPath()
|
||||||
|
+ QStringLiteral("/Plugins/RcNetBridge.dll"));
|
||||||
|
|
||||||
|
if (!QFileInfo::exists(bridgePath)) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"RcNetBridge.dll not found in the Plugins folder.\n"
|
||||||
|
"Expected at: %1").arg(bridgePath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_fns = {};
|
||||||
|
QString nativePath = QDir::toNativeSeparators(path);
|
||||||
|
if (!m_clrHost->loadManagedPlugin(bridgePath, nativePath, &m_fns, errorMsg))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_dllPath = path;
|
||||||
|
m_isManaged = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAS_CLR_BRIDGE
|
||||||
|
|
||||||
|
// --IProviderPlugin ------------------------------------------------------
|
||||||
|
|
||||||
|
bool RcNetCompatPlugin::canHandle(const QString& target) const
|
||||||
|
{
|
||||||
|
// Target format: "dllpath|pid:name"
|
||||||
|
return target.contains('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<rcx::Provider> RcNetCompatPlugin::createProvider(
|
||||||
|
const QString& target, QString* errorMsg)
|
||||||
|
{
|
||||||
|
// Parse "dllpath|pid:name"
|
||||||
|
int sep = target.indexOf('|');
|
||||||
|
if (sep < 0) {
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("Invalid target format");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString dllPath = target.left(sep);
|
||||||
|
QString pidPart = target.mid(sep + 1);
|
||||||
|
|
||||||
|
// Load (or reuse) the plugin DLL
|
||||||
|
if (!loadPlugin(dllPath, errorMsg))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Parse pid:name
|
||||||
|
QStringList parts = pidPart.split(':');
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) {
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID: %1").arg(parts[0]);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
QString procName = parts.size() > 1 ? parts[1] : QStringLiteral("PID %1").arg(pid);
|
||||||
|
|
||||||
|
auto provider = std::make_unique<RcNetCompatProvider>(m_fns, pid, procName);
|
||||||
|
if (!provider->isValid()) {
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral(
|
||||||
|
"Failed to open process %1 (PID: %2) via ReClass.NET plugin.\n"
|
||||||
|
"Ensure the process is running and the plugin supports it.")
|
||||||
|
.arg(procName).arg(pid);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t RcNetCompatPlugin::getInitialBaseAddress(const QString& target) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(target);
|
||||||
|
// The provider sets its own base from module enumeration.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RcNetCompatPlugin::selectTarget(QWidget* parent, QString* target)
|
||||||
|
{
|
||||||
|
// Step 1: Pick a ReClass.NET plugin DLL (native or .NET)
|
||||||
|
QString dllPath = QFileDialog::getOpenFileName(
|
||||||
|
parent,
|
||||||
|
QStringLiteral("Select ReClass.NET Plugin"),
|
||||||
|
QString(),
|
||||||
|
QStringLiteral("DLL Files (*.dll)"));
|
||||||
|
|
||||||
|
if (dllPath.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Step 2: Load and validate the DLL
|
||||||
|
QString loadErr;
|
||||||
|
if (!loadPlugin(dllPath, &loadErr)) {
|
||||||
|
QMessageBox::warning(parent,
|
||||||
|
QStringLiteral("ReClass.NET Compat Layer"),
|
||||||
|
loadErr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Enumerate processes and show picker
|
||||||
|
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
||||||
|
|
||||||
|
QList<ProcessInfo> processes;
|
||||||
|
for (const auto& p : pluginProcesses) {
|
||||||
|
ProcessInfo info;
|
||||||
|
info.pid = p.pid;
|
||||||
|
info.name = p.name;
|
||||||
|
info.path = p.path;
|
||||||
|
info.icon = p.icon;
|
||||||
|
processes.append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessPicker picker(processes, parent);
|
||||||
|
if (picker.exec() != QDialog::Accepted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32_t pid = picker.selectedProcessId();
|
||||||
|
QString name = picker.selectedProcessName();
|
||||||
|
|
||||||
|
// Step 4: Format target as "dllpath|pid:name"
|
||||||
|
*target = QStringLiteral("%1|%2:%3").arg(dllPath).arg(pid).arg(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --Process enumeration --------------------------------------------------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct ProcessCollector {
|
||||||
|
QVector<PluginProcessInfo>* dest = nullptr;
|
||||||
|
};
|
||||||
|
thread_local ProcessCollector g_processCollector;
|
||||||
|
|
||||||
|
void RC_CALLCONV processCallback(EnumerateProcessData* data)
|
||||||
|
{
|
||||||
|
if (!data || !g_processCollector.dest) return;
|
||||||
|
|
||||||
|
PluginProcessInfo info;
|
||||||
|
info.pid = static_cast<uint32_t>(data->Id);
|
||||||
|
info.name = QString::fromUtf16(data->Name);
|
||||||
|
info.path = QString::fromUtf16(data->Path);
|
||||||
|
g_processCollector.dest->append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
QVector<PluginProcessInfo> RcNetCompatPlugin::enumerateProcesses()
|
||||||
|
{
|
||||||
|
QVector<PluginProcessInfo> result;
|
||||||
|
|
||||||
|
if (!m_fns.EnumerateProcesses)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
g_processCollector.dest = &result;
|
||||||
|
m_fns.EnumerateProcesses(processCallback);
|
||||||
|
g_processCollector.dest = nullptr;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --Plugin factory -------------------------------------------------------
|
||||||
|
|
||||||
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||||
|
{
|
||||||
|
return new RcNetCompatPlugin();
|
||||||
|
}
|
||||||
61
plugins/RcNetPluginCompatLayer/RcNetCompatPlugin.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../../src/iplugin.h"
|
||||||
|
#include "ReClassNET_Plugin.hpp"
|
||||||
|
|
||||||
|
#include <QLibrary>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef HAS_CLR_BRIDGE
|
||||||
|
#include "ClrHost.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ReclassX plugin that loads ReClass.NET plugin DLLs
|
||||||
|
* and exposes them as ReclassX providers.
|
||||||
|
*
|
||||||
|
* Supports both native DLLs (C exports) and, when built with
|
||||||
|
* HAS_CLR_BRIDGE, managed .NET assemblies via in-process CLR hosting.
|
||||||
|
*
|
||||||
|
* Target string format: "dllpath|pid:processname"
|
||||||
|
*/
|
||||||
|
class RcNetCompatPlugin : public IProviderPlugin
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Plugin metadata
|
||||||
|
std::string Name() const override { return "ReClass.NET Compat Layer"; }
|
||||||
|
std::string Version() const override { return "1.0.0"; }
|
||||||
|
std::string Author() const override { return "Reclass"; }
|
||||||
|
std::string Description() const override {
|
||||||
|
return "Loads ReClass.NET native and .NET plugin DLLs as Reclass data sources";
|
||||||
|
}
|
||||||
|
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||||
|
QIcon Icon() const override;
|
||||||
|
|
||||||
|
// IProviderPlugin interface
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Override process enumeration -- we enumerate via the loaded DLL
|
||||||
|
bool providesProcessList() const override { return true; }
|
||||||
|
QVector<PluginProcessInfo> enumerateProcesses() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loadPlugin(const QString& path, QString* errorMsg = nullptr);
|
||||||
|
bool loadNativeDll(const QString& path, QString* errorMsg = nullptr);
|
||||||
|
void unloadNativeDll();
|
||||||
|
|
||||||
|
#ifdef HAS_CLR_BRIDGE
|
||||||
|
bool loadManagedDll(const QString& path, QString* errorMsg = nullptr);
|
||||||
|
std::unique_ptr<ClrHost> m_clrHost;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::unique_ptr<QLibrary> m_lib;
|
||||||
|
RcNetFunctions m_fns;
|
||||||
|
QString m_dllPath;
|
||||||
|
bool m_isManaged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plugin export
|
||||||
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||||
132
plugins/RcNetPluginCompatLayer/RcNetCompatProvider.cpp
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#include "RcNetCompatProvider.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// -- Construction / destruction -------------------------------------------
|
||||||
|
|
||||||
|
RcNetCompatProvider::RcNetCompatProvider(const RcNetFunctions& fns,
|
||||||
|
uint32_t pid,
|
||||||
|
const QString& processName)
|
||||||
|
: m_fns(fns)
|
||||||
|
, m_pid(pid)
|
||||||
|
, m_processName(processName)
|
||||||
|
{
|
||||||
|
if (m_fns.OpenRemoteProcess)
|
||||||
|
m_handle = m_fns.OpenRemoteProcess(static_cast<RC_Size>(pid),
|
||||||
|
ProcessAccess::Full);
|
||||||
|
|
||||||
|
if (m_handle)
|
||||||
|
cacheModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
RcNetCompatProvider::~RcNetCompatProvider()
|
||||||
|
{
|
||||||
|
if (m_handle && m_fns.CloseRemoteProcess)
|
||||||
|
m_fns.CloseRemoteProcess(m_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Required overrides ---------------------------------------------------
|
||||||
|
|
||||||
|
bool RcNetCompatProvider::read(uint64_t addr, void* buf, int len) const
|
||||||
|
{
|
||||||
|
if (!m_handle || !m_fns.ReadRemoteMemory || len <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_fns.ReadRemoteMemory(m_handle,
|
||||||
|
reinterpret_cast<RC_Pointer>(addr),
|
||||||
|
static_cast<RC_Pointer>(buf),
|
||||||
|
0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RcNetCompatProvider::size() const
|
||||||
|
{
|
||||||
|
if (!m_handle) return 0;
|
||||||
|
if (m_fns.IsProcessValid && !m_fns.IsProcessValid(m_handle)) return 0;
|
||||||
|
return 0x10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Optional overrides ---------------------------------------------------
|
||||||
|
|
||||||
|
bool RcNetCompatProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
|
{
|
||||||
|
if (!m_handle || !m_fns.WriteRemoteMemory || len <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_fns.WriteRemoteMemory(m_handle,
|
||||||
|
reinterpret_cast<RC_Pointer>(addr),
|
||||||
|
const_cast<RC_Pointer>(static_cast<const void*>(buf)),
|
||||||
|
0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RcNetCompatProvider::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 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 {
|
||||||
|
|
||||||
|
// Thread-local collector for the module enumeration callback.
|
||||||
|
// ReClass.NET callbacks are synchronous, so this is safe.
|
||||||
|
struct ModuleCollector {
|
||||||
|
QVector<RcNetCompatProvider::ModuleInfo>* dest = nullptr;
|
||||||
|
};
|
||||||
|
thread_local ModuleCollector g_moduleCollector;
|
||||||
|
|
||||||
|
void RC_CALLCONV moduleCallback(EnumerateRemoteModuleData* data)
|
||||||
|
{
|
||||||
|
if (!data || !g_moduleCollector.dest) return;
|
||||||
|
|
||||||
|
QString path = QString::fromUtf16(data->Path);
|
||||||
|
QFileInfo fi(path);
|
||||||
|
|
||||||
|
RcNetCompatProvider::ModuleInfo info;
|
||||||
|
info.name = fi.fileName();
|
||||||
|
info.base = reinterpret_cast<uint64_t>(data->BaseAddress);
|
||||||
|
info.size = static_cast<uint64_t>(data->Size);
|
||||||
|
g_moduleCollector.dest->append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still need a section callback even though we don't use it.
|
||||||
|
void RC_CALLCONV sectionCallback(EnumerateRemoteSectionData*)
|
||||||
|
{
|
||||||
|
// Intentionally empty -- we only need module data.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
void RcNetCompatProvider::cacheModules()
|
||||||
|
{
|
||||||
|
if (!m_fns.EnumerateRemoteSectionsAndModules || !m_handle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_modules.clear();
|
||||||
|
g_moduleCollector.dest = &m_modules;
|
||||||
|
m_fns.EnumerateRemoteSectionsAndModules(m_handle, sectionCallback, moduleCallback);
|
||||||
|
g_moduleCollector.dest = nullptr;
|
||||||
|
|
||||||
|
// Set base to first module if we got any
|
||||||
|
if (!m_modules.isEmpty() && m_base == 0)
|
||||||
|
m_base = m_modules.first().base;
|
||||||
|
}
|
||||||
48
plugins/RcNetPluginCompatLayer/RcNetCompatProvider.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../../src/providers/provider.h"
|
||||||
|
#include "ReClassNET_Plugin.hpp"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider that bridges ReClass.NET native plugin DLL calls
|
||||||
|
* to the ReclassX Provider interface.
|
||||||
|
*/
|
||||||
|
class RcNetCompatProvider : public rcx::Provider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RcNetCompatProvider(const RcNetFunctions& fns, uint32_t pid,
|
||||||
|
const QString& processName);
|
||||||
|
~RcNetCompatProvider() override;
|
||||||
|
|
||||||
|
// Required overrides
|
||||||
|
bool read(uint64_t addr, void* buf, int len) const override;
|
||||||
|
int size() const override;
|
||||||
|
|
||||||
|
// Optional overrides
|
||||||
|
bool write(uint64_t addr, const void* buf, int len) override;
|
||||||
|
bool isWritable() const override { return m_fns.WriteRemoteMemory != nullptr; }
|
||||||
|
QString name() const override { return m_processName; }
|
||||||
|
QString kind() const override { return QStringLiteral("RcNet"); }
|
||||||
|
bool isLive() const override { return true; }
|
||||||
|
uint64_t base() const override { return m_base; }
|
||||||
|
QString getSymbol(uint64_t addr) const override;
|
||||||
|
uint64_t symbolToAddress(const QString& name) const override;
|
||||||
|
|
||||||
|
struct ModuleInfo {
|
||||||
|
QString name;
|
||||||
|
uint64_t base;
|
||||||
|
uint64_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cacheModules();
|
||||||
|
|
||||||
|
RcNetFunctions m_fns;
|
||||||
|
RC_Pointer m_handle = nullptr;
|
||||||
|
uint32_t m_pid;
|
||||||
|
QString m_processName;
|
||||||
|
uint64_t m_base = 0;
|
||||||
|
QVector<ModuleInfo> m_modules;
|
||||||
|
};
|
||||||
140
plugins/RcNetPluginCompatLayer/ReClassNET_Plugin.hpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#pragma once
|
||||||
|
// Subset of ReClass.NET native plugin types needed for the compatibility layer.
|
||||||
|
// Based on the ReClass.NET NativeCore plugin interface.
|
||||||
|
// Only types required by the 8 supported exports are included (no debug types).
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define RC_CALLCONV __stdcall
|
||||||
|
#else
|
||||||
|
#define RC_CALLCONV
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// -- Basic types ----------------------------------------------------------
|
||||||
|
|
||||||
|
using RC_Pointer = void*;
|
||||||
|
using RC_Size = uint64_t;
|
||||||
|
using RC_UnicodeChar = char16_t;
|
||||||
|
|
||||||
|
// -- Enums ----------------------------------------------------------------
|
||||||
|
|
||||||
|
enum class ProcessAccess
|
||||||
|
{
|
||||||
|
Read = 0,
|
||||||
|
Write = 1,
|
||||||
|
Full = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SectionProtection
|
||||||
|
{
|
||||||
|
NoAccess = 0,
|
||||||
|
Read = 1,
|
||||||
|
Write = 2,
|
||||||
|
Execute = 4,
|
||||||
|
Guard = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SectionType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Private = 1,
|
||||||
|
Mapped = 2,
|
||||||
|
Image = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SectionCategory
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
CODE = 1,
|
||||||
|
DATA = 2,
|
||||||
|
HEAP = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ControlRemoteProcessAction
|
||||||
|
{
|
||||||
|
Suspend = 0,
|
||||||
|
Resume = 1,
|
||||||
|
Terminate = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// -- Callback data structures ---------------------------------------------
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
struct EnumerateProcessData
|
||||||
|
{
|
||||||
|
RC_Size Id;
|
||||||
|
RC_UnicodeChar Name[260];
|
||||||
|
RC_UnicodeChar Path[260];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EnumerateRemoteSectionData
|
||||||
|
{
|
||||||
|
RC_Pointer BaseAddress;
|
||||||
|
RC_Size Size;
|
||||||
|
SectionType Type;
|
||||||
|
SectionCategory Category;
|
||||||
|
SectionProtection Protection;
|
||||||
|
RC_UnicodeChar Name[16];
|
||||||
|
RC_UnicodeChar ModulePath[260];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EnumerateRemoteModuleData
|
||||||
|
{
|
||||||
|
RC_Pointer BaseAddress;
|
||||||
|
RC_Size Size;
|
||||||
|
RC_UnicodeChar Path[260];
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// -- Callback typedefs ----------------------------------------------------
|
||||||
|
|
||||||
|
using EnumerateProcessCallback = void(RC_CALLCONV*)(EnumerateProcessData* data);
|
||||||
|
using EnumerateRemoteSectionsCallback = void(RC_CALLCONV*)(EnumerateRemoteSectionData* data);
|
||||||
|
using EnumerateRemoteModulesCallback = void(RC_CALLCONV*)(EnumerateRemoteModuleData* data);
|
||||||
|
|
||||||
|
// -- Function pointer typedefs for resolved exports -----------------------
|
||||||
|
|
||||||
|
using FnEnumerateProcesses = void(RC_CALLCONV*)(EnumerateProcessCallback callback);
|
||||||
|
|
||||||
|
using FnOpenRemoteProcess = RC_Pointer(RC_CALLCONV*)(RC_Size id, ProcessAccess desiredAccess);
|
||||||
|
|
||||||
|
using FnIsProcessValid = bool(RC_CALLCONV*)(RC_Pointer handle);
|
||||||
|
|
||||||
|
using FnCloseRemoteProcess = void(RC_CALLCONV*)(RC_Pointer handle);
|
||||||
|
|
||||||
|
using FnReadRemoteMemory = bool(RC_CALLCONV*)(RC_Pointer handle,
|
||||||
|
RC_Pointer address,
|
||||||
|
RC_Pointer buffer,
|
||||||
|
int offset,
|
||||||
|
int size);
|
||||||
|
|
||||||
|
using FnWriteRemoteMemory = bool(RC_CALLCONV*)(RC_Pointer handle,
|
||||||
|
RC_Pointer address,
|
||||||
|
RC_Pointer buffer,
|
||||||
|
int offset,
|
||||||
|
int size);
|
||||||
|
|
||||||
|
using FnEnumerateRemoteSectionsAndModules =
|
||||||
|
void(RC_CALLCONV*)(RC_Pointer handle,
|
||||||
|
EnumerateRemoteSectionsCallback sectionCallback,
|
||||||
|
EnumerateRemoteModulesCallback moduleCallback);
|
||||||
|
|
||||||
|
using FnControlRemoteProcess = void(RC_CALLCONV*)(RC_Pointer handle,
|
||||||
|
ControlRemoteProcessAction action);
|
||||||
|
|
||||||
|
// -- Resolved function table ----------------------------------------------
|
||||||
|
|
||||||
|
struct RcNetFunctions
|
||||||
|
{
|
||||||
|
FnEnumerateProcesses EnumerateProcesses = nullptr;
|
||||||
|
FnOpenRemoteProcess OpenRemoteProcess = nullptr;
|
||||||
|
FnIsProcessValid IsProcessValid = nullptr;
|
||||||
|
FnCloseRemoteProcess CloseRemoteProcess = nullptr;
|
||||||
|
FnReadRemoteMemory ReadRemoteMemory = nullptr;
|
||||||
|
FnWriteRemoteMemory WriteRemoteMemory = nullptr;
|
||||||
|
FnEnumerateRemoteSectionsAndModules EnumerateRemoteSectionsAndModules = nullptr;
|
||||||
|
FnControlRemoteProcess ControlRemoteProcess = nullptr;
|
||||||
|
};
|
||||||
677
plugins/RcNetPluginCompatLayer/bridge/RcNetBridge.cs
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
// RcNetBridge -- in-process C# bridge for loading .NET ReClass.NET plugins.
|
||||||
|
//
|
||||||
|
// Called from C++ via ICLRRuntimeHost::ExecuteInDefaultAppDomain().
|
||||||
|
// The single entry point is Bridge.Initialize(string arg) where arg is:
|
||||||
|
// "<hex_address_of_RcNetFunctions>|<plugin_dll_path>"
|
||||||
|
//
|
||||||
|
// The bridge:
|
||||||
|
// 1. Registers an AssemblyResolve handler that provides THIS assembly
|
||||||
|
// when a plugin asks for "ReClassNET", so the stub types below satisfy
|
||||||
|
// the plugin's type references.
|
||||||
|
// 2. Loads the plugin assembly and finds an ICoreProcessFunctions
|
||||||
|
// implementation.
|
||||||
|
// 3. Creates [UnmanagedFunctionPointer] delegates wrapping each method.
|
||||||
|
// 4. Writes the native-callable function pointers into the RcNetFunctions
|
||||||
|
// struct at the address provided by C++.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// ReClass.NET stub types
|
||||||
|
// These mirror the subset of types from the ReClass.NET assembly that
|
||||||
|
// memory-reading plugins reference. When the CLR resolves "ReClassNET"
|
||||||
|
// via our AssemblyResolve handler, it gets THIS assembly, and these types
|
||||||
|
// satisfy the plugin's type references.
|
||||||
|
//
|
||||||
|
// Types are placed in the exact namespaces used by the real ReClass.NET
|
||||||
|
// assembly so that plugins compiled against it resolve correctly.
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// ReClassNET.Memory -- section enums (referenced by EnumerateRemoteSectionData)
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Memory
|
||||||
|
{
|
||||||
|
public enum SectionProtection
|
||||||
|
{
|
||||||
|
NoAccess = 0,
|
||||||
|
Read = 1,
|
||||||
|
Write = 2,
|
||||||
|
Execute = 4,
|
||||||
|
Guard = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SectionType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Private = 1,
|
||||||
|
Mapped = 2,
|
||||||
|
Image = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SectionCategory
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
CODE = 1,
|
||||||
|
DATA = 2,
|
||||||
|
HEAP = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// ReClassNET.Debugger -- debugger types (used by ICoreProcessFunctions)
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Debugger
|
||||||
|
{
|
||||||
|
public enum DebugContinueStatus
|
||||||
|
{
|
||||||
|
Handled = 0,
|
||||||
|
NotHandled = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HardwareBreakpointRegister
|
||||||
|
{
|
||||||
|
InvalidRegister = 0,
|
||||||
|
Dr0 = 1,
|
||||||
|
Dr1 = 2,
|
||||||
|
Dr2 = 3,
|
||||||
|
Dr3 = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HardwareBreakpointTrigger
|
||||||
|
{
|
||||||
|
Execute = 0,
|
||||||
|
Access = 1,
|
||||||
|
Write = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HardwareBreakpointSize
|
||||||
|
{
|
||||||
|
Size1 = 1,
|
||||||
|
Size2 = 2,
|
||||||
|
Size4 = 4,
|
||||||
|
Size8 = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ExceptionDebugInfo
|
||||||
|
{
|
||||||
|
public IntPtr ExceptionCode;
|
||||||
|
public IntPtr ExceptionFlags;
|
||||||
|
public IntPtr ExceptionAddress;
|
||||||
|
public HardwareBreakpointRegister CausedBy;
|
||||||
|
public RegisterInfo Registers;
|
||||||
|
|
||||||
|
public struct RegisterInfo
|
||||||
|
{
|
||||||
|
public IntPtr Rax, Rbx, Rcx, Rdx;
|
||||||
|
public IntPtr Rdi, Rsi, Rsp, Rbp, Rip;
|
||||||
|
public IntPtr R8, R9, R10, R11, R12, R13, R14, R15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DebugEvent
|
||||||
|
{
|
||||||
|
public DebugContinueStatus ContinueStatus;
|
||||||
|
public IntPtr ProcessId;
|
||||||
|
public IntPtr ThreadId;
|
||||||
|
public ExceptionDebugInfo ExceptionInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// ReClassNET.Core -- interface, enums, delegates, and data structs
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Core
|
||||||
|
{
|
||||||
|
public enum ProcessAccess
|
||||||
|
{
|
||||||
|
Read = 0,
|
||||||
|
Write = 1,
|
||||||
|
Full = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ControlRemoteProcessAction
|
||||||
|
{
|
||||||
|
Suspend = 0,
|
||||||
|
Resume = 1,
|
||||||
|
Terminate = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EnumerateProcessData
|
||||||
|
{
|
||||||
|
public IntPtr Id;
|
||||||
|
public string Name;
|
||||||
|
public string Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EnumerateRemoteSectionData
|
||||||
|
{
|
||||||
|
public IntPtr BaseAddress;
|
||||||
|
public IntPtr Size;
|
||||||
|
public ReClassNET.Memory.SectionType Type;
|
||||||
|
public ReClassNET.Memory.SectionCategory Category;
|
||||||
|
public ReClassNET.Memory.SectionProtection Protection;
|
||||||
|
public string Name;
|
||||||
|
public string ModulePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EnumerateRemoteModuleData
|
||||||
|
{
|
||||||
|
public IntPtr BaseAddress;
|
||||||
|
public IntPtr Size;
|
||||||
|
public string Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void EnumerateProcessCallback(ref EnumerateProcessData data);
|
||||||
|
public delegate void EnumerateRemoteSectionCallback(ref EnumerateRemoteSectionData data);
|
||||||
|
public delegate void EnumerateRemoteModuleCallback(ref EnumerateRemoteModuleData data);
|
||||||
|
|
||||||
|
public interface ICoreProcessFunctions
|
||||||
|
{
|
||||||
|
void EnumerateProcesses(EnumerateProcessCallback callbackProcess);
|
||||||
|
IntPtr OpenRemoteProcess(IntPtr pid, ProcessAccess desiredAccess);
|
||||||
|
bool IsProcessValid(IntPtr process);
|
||||||
|
void CloseRemoteProcess(IntPtr process);
|
||||||
|
bool ReadRemoteMemory(IntPtr process, IntPtr address, ref byte[] buffer, int offset, int size);
|
||||||
|
bool WriteRemoteMemory(IntPtr process, IntPtr address, ref byte[] buffer, int offset, int size);
|
||||||
|
void EnumerateRemoteSectionsAndModules(
|
||||||
|
IntPtr process,
|
||||||
|
EnumerateRemoteSectionCallback callbackSection,
|
||||||
|
EnumerateRemoteModuleCallback callbackModule);
|
||||||
|
void ControlRemoteProcess(IntPtr process, ControlRemoteProcessAction action);
|
||||||
|
|
||||||
|
// Debugger methods -- stubs required for interface compatibility
|
||||||
|
bool AttachDebuggerToProcess(IntPtr id);
|
||||||
|
void DetachDebuggerFromProcess(IntPtr id);
|
||||||
|
bool AwaitDebugEvent(ref ReClassNET.Debugger.DebugEvent evt, int timeoutInMilliseconds);
|
||||||
|
void HandleDebugEvent(ref ReClassNET.Debugger.DebugEvent evt);
|
||||||
|
bool SetHardwareBreakpoint(IntPtr id, IntPtr address,
|
||||||
|
ReClassNET.Debugger.HardwareBreakpointRegister register,
|
||||||
|
ReClassNET.Debugger.HardwareBreakpointTrigger trigger,
|
||||||
|
ReClassNET.Debugger.HardwareBreakpointSize size,
|
||||||
|
bool set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// ReClassNET.Memory -- RemoteProcess stub
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Memory
|
||||||
|
{
|
||||||
|
public class RemoteProcess { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// ReClassNET.Logger -- ILogger stub
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Logger
|
||||||
|
{
|
||||||
|
public interface ILogger { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Stub types for IPluginHost properties
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Forms
|
||||||
|
{
|
||||||
|
public class MainForm { }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ReClassNET
|
||||||
|
{
|
||||||
|
public class Settings { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// ReClassNET.Plugins
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
namespace ReClassNET.Plugins
|
||||||
|
{
|
||||||
|
public abstract class Plugin : IDisposable
|
||||||
|
{
|
||||||
|
public virtual bool Initialize(IPluginHost host) { return true; }
|
||||||
|
public virtual void Terminate() { }
|
||||||
|
public virtual void Dispose() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPluginHost
|
||||||
|
{
|
||||||
|
ReClassNET.Forms.MainForm MainWindow { get; }
|
||||||
|
System.Resources.ResourceManager Resources { get; }
|
||||||
|
ReClassNET.Memory.RemoteProcess Process { get; }
|
||||||
|
ReClassNET.Logger.ILogger Logger { get; }
|
||||||
|
ReClassNET.Settings Settings { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Bridge
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
namespace RcNetBridge
|
||||||
|
{
|
||||||
|
internal class StubPluginHost : ReClassNET.Plugins.IPluginHost
|
||||||
|
{
|
||||||
|
public ReClassNET.Forms.MainForm MainWindow => null;
|
||||||
|
public System.Resources.ResourceManager Resources => null;
|
||||||
|
public ReClassNET.Memory.RemoteProcess Process => null;
|
||||||
|
public ReClassNET.Logger.ILogger Logger => null;
|
||||||
|
public ReClassNET.Settings Settings => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Bridge
|
||||||
|
{
|
||||||
|
// -- Persistent state (static so it survives after Initialize returns) --
|
||||||
|
|
||||||
|
private static ReClassNET.Core.ICoreProcessFunctions s_functions;
|
||||||
|
private static readonly List<Delegate> s_pinned = new List<Delegate>();
|
||||||
|
|
||||||
|
// -- Entry point called from C++ --------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by ICLRRuntimeHost::ExecuteInDefaultAppDomain.
|
||||||
|
/// arg = "<hex_address_of_RcNetFunctions>|<plugin_dll_path>"
|
||||||
|
/// Returns 0 on success, non-zero error code on failure.
|
||||||
|
/// </summary>
|
||||||
|
public static int Initialize(string arg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int sep = arg.IndexOf('|');
|
||||||
|
if (sep < 0) return 1; // bad arg
|
||||||
|
|
||||||
|
long ptrValue = long.Parse(arg.Substring(0, sep), NumberStyles.HexNumber);
|
||||||
|
IntPtr funcTablePtr = new IntPtr(ptrValue);
|
||||||
|
string pluginPath = arg.Substring(sep + 1);
|
||||||
|
|
||||||
|
// Set up assembly resolution
|
||||||
|
string pluginDir = Path.GetDirectoryName(pluginPath) ?? ".";
|
||||||
|
string parentDir = Path.GetDirectoryName(pluginDir);
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += (sender, resolveArgs) =>
|
||||||
|
{
|
||||||
|
string asmName = new AssemblyName(resolveArgs.Name).Name;
|
||||||
|
|
||||||
|
// Provide our own assembly as the "ReClass.NET" stub
|
||||||
|
if (string.Equals(asmName, "ReClass.NET", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return typeof(Bridge).Assembly;
|
||||||
|
|
||||||
|
// Search plugin directory and parent for other dependencies
|
||||||
|
string dllName = asmName + ".dll";
|
||||||
|
foreach (string dir in new[] { pluginDir, parentDir })
|
||||||
|
{
|
||||||
|
if (dir == null) continue;
|
||||||
|
string path = Path.Combine(dir, dllName);
|
||||||
|
if (File.Exists(path))
|
||||||
|
return Assembly.LoadFrom(path);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load plugin and find ICoreProcessFunctions
|
||||||
|
if (!LoadPlugin(pluginPath))
|
||||||
|
return 2; // no implementation found
|
||||||
|
|
||||||
|
// Write function pointers
|
||||||
|
WriteFunctionPointers(funcTablePtr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is ReflectionTypeLoadException || ex is FileNotFoundException)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Plugin loading ---------------------------------------------------
|
||||||
|
|
||||||
|
private static bool LoadPlugin(string pluginPath)
|
||||||
|
{
|
||||||
|
Assembly asm = Assembly.LoadFrom(pluginPath);
|
||||||
|
|
||||||
|
// Find a concrete type that implements ICoreProcessFunctions.
|
||||||
|
// ReClass.NET plugins typically extend Plugin and directly
|
||||||
|
// implement ICoreProcessFunctions on the same class.
|
||||||
|
foreach (Type type in asm.GetExportedTypes())
|
||||||
|
{
|
||||||
|
if (type.IsAbstract || type.IsInterface) continue;
|
||||||
|
|
||||||
|
Type iface = type.GetInterfaces().FirstOrDefault(i =>
|
||||||
|
i.FullName == "ReClassNET.Core.ICoreProcessFunctions");
|
||||||
|
if (iface == null) continue;
|
||||||
|
|
||||||
|
object instance = Activator.CreateInstance(type);
|
||||||
|
|
||||||
|
// Try calling Initialize() but don't fail if it throws --
|
||||||
|
// plugins use it for UI integration with the host app,
|
||||||
|
// which we can't fully provide. The process functions
|
||||||
|
// (ReadRemoteMemory, etc.) work without it.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MethodInfo init = type.GetMethod("Initialize",
|
||||||
|
BindingFlags.Public | BindingFlags.Instance,
|
||||||
|
null, new[] { typeof(ReClassNET.Plugins.IPluginHost) }, null);
|
||||||
|
if (init != null)
|
||||||
|
init.Invoke(instance, new object[] { new StubPluginHost() });
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
s_functions = (ReClassNET.Core.ICoreProcessFunctions)instance;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Native-callable delegate types -----------------------------------
|
||||||
|
// These match the C++ RcNetFunctions struct field order exactly.
|
||||||
|
// On x64 Windows all calling conventions collapse to the Microsoft
|
||||||
|
// x64 ABI, so StdCall is used for documentation / x86 correctness.
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void DelEnumProcesses(IntPtr callback);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate IntPtr DelOpenRemoteProcess(ulong id, int access);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
[return: MarshalAs(UnmanagedType.I1)]
|
||||||
|
delegate bool DelIsProcessValid(IntPtr handle);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void DelCloseRemoteProcess(IntPtr handle);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
[return: MarshalAs(UnmanagedType.I1)]
|
||||||
|
delegate bool DelReadRemoteMemory(IntPtr handle, IntPtr address,
|
||||||
|
IntPtr buffer, int offset, int size);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
[return: MarshalAs(UnmanagedType.I1)]
|
||||||
|
delegate bool DelWriteRemoteMemory(IntPtr handle, IntPtr address,
|
||||||
|
IntPtr buffer, int offset, int size);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void DelEnumSectionsAndModules(IntPtr handle,
|
||||||
|
IntPtr sectionCallback, IntPtr moduleCallback);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void DelControlRemoteProcess(IntPtr handle, int action);
|
||||||
|
|
||||||
|
// Callback delegate types -- these point into C++ and are called by us.
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void NativeProcessCallback(IntPtr data);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void NativeSectionCallback(IntPtr data);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
delegate void NativeModuleCallback(IntPtr data);
|
||||||
|
|
||||||
|
// -- Write function pointers to the C++ struct ------------------------
|
||||||
|
|
||||||
|
private static void WriteFunctionPointers(IntPtr funcTable)
|
||||||
|
{
|
||||||
|
// RcNetFunctions layout: 8 consecutive function pointers.
|
||||||
|
int i = 0;
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelEnumProcesses>(EnumProcessesImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelOpenRemoteProcess>(OpenProcessImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelIsProcessValid>(IsProcessValidImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelCloseRemoteProcess>(CloseProcessImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelReadRemoteMemory>(ReadMemoryImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelWriteRemoteMemory>(WriteMemoryImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelEnumSectionsAndModules>(EnumSectionsModulesImpl));
|
||||||
|
WriteSlot(funcTable, i++, Pin<DelControlRemoteProcess>(ControlProcessImpl));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IntPtr Pin<T>(T del) where T : class
|
||||||
|
{
|
||||||
|
Delegate d = del as Delegate;
|
||||||
|
s_pinned.Add(d); // prevent GC
|
||||||
|
return Marshal.GetFunctionPointerForDelegate(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteSlot(IntPtr table, int index, IntPtr value)
|
||||||
|
{
|
||||||
|
Marshal.WriteIntPtr(table, index * IntPtr.Size, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Implementation methods -------------------------------------------
|
||||||
|
|
||||||
|
// -- EnumerateProcesses --
|
||||||
|
// C++ passes a native callback; we call the plugin, convert each
|
||||||
|
// managed EnumerateProcessData to the packed native layout, and
|
||||||
|
// forward to the native callback.
|
||||||
|
|
||||||
|
private static void EnumProcessesImpl(IntPtr nativeCallbackPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_functions == null || nativeCallbackPtr == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
NativeProcessCallback nativeCb =
|
||||||
|
Marshal.GetDelegateForFunctionPointer<NativeProcessCallback>(nativeCallbackPtr);
|
||||||
|
|
||||||
|
// Native layout (pack=1): uint64 Id + char16[260] Name + char16[260] Path
|
||||||
|
const int kStructSize = 8 + 520 + 520; // 1048 bytes
|
||||||
|
|
||||||
|
s_functions.EnumerateProcesses(
|
||||||
|
(ref ReClassNET.Core.EnumerateProcessData data) =>
|
||||||
|
{
|
||||||
|
IntPtr mem = Marshal.AllocHGlobal(kStructSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Zero-fill
|
||||||
|
byte[] zeros = new byte[kStructSize];
|
||||||
|
Marshal.Copy(zeros, 0, mem, kStructSize);
|
||||||
|
|
||||||
|
// Id (8 bytes at offset 0)
|
||||||
|
Marshal.WriteInt64(mem, 0, data.Id.ToInt64());
|
||||||
|
|
||||||
|
// Name (char16[260] at offset 8)
|
||||||
|
if (data.Name != null)
|
||||||
|
{
|
||||||
|
char[] chars = data.Name.ToCharArray();
|
||||||
|
int count = Math.Min(chars.Length, 259);
|
||||||
|
Marshal.Copy(chars, 0, new IntPtr(mem.ToInt64() + 8), count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path (char16[260] at offset 528)
|
||||||
|
if (data.Path != null)
|
||||||
|
{
|
||||||
|
char[] chars = data.Path.ToCharArray();
|
||||||
|
int count = Math.Min(chars.Length, 259);
|
||||||
|
Marshal.Copy(chars, 0, new IntPtr(mem.ToInt64() + 528), count);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeCb(mem);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(mem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch { /* swallow -- don't crash the host process */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- OpenRemoteProcess --
|
||||||
|
private static IntPtr OpenProcessImpl(ulong id, int access)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_functions == null) return IntPtr.Zero;
|
||||||
|
return s_functions.OpenRemoteProcess(
|
||||||
|
new IntPtr((long)id),
|
||||||
|
(ReClassNET.Core.ProcessAccess)access);
|
||||||
|
}
|
||||||
|
catch { return IntPtr.Zero; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- IsProcessValid --
|
||||||
|
private static bool IsProcessValidImpl(IntPtr handle)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_functions == null) return false;
|
||||||
|
return s_functions.IsProcessValid(handle);
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- CloseRemoteProcess --
|
||||||
|
private static void CloseProcessImpl(IntPtr handle)
|
||||||
|
{
|
||||||
|
try { s_functions?.CloseRemoteProcess(handle); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- ReadRemoteMemory --
|
||||||
|
// C++ provides a native buffer pointer. We read into a managed array
|
||||||
|
// via the plugin's interface, then copy to the native buffer.
|
||||||
|
private static bool ReadMemoryImpl(IntPtr handle, IntPtr address,
|
||||||
|
IntPtr buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_functions == null || size <= 0) return false;
|
||||||
|
|
||||||
|
byte[] managed = new byte[size];
|
||||||
|
bool ok = s_functions.ReadRemoteMemory(
|
||||||
|
handle, address, ref managed, 0, size);
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
Marshal.Copy(managed, 0, new IntPtr(buffer.ToInt64() + offset), size);
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- WriteRemoteMemory --
|
||||||
|
private static bool WriteMemoryImpl(IntPtr handle, IntPtr address,
|
||||||
|
IntPtr buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_functions == null || size <= 0) return false;
|
||||||
|
|
||||||
|
byte[] managed = new byte[size];
|
||||||
|
Marshal.Copy(new IntPtr(buffer.ToInt64() + offset), managed, 0, size);
|
||||||
|
|
||||||
|
return s_functions.WriteRemoteMemory(
|
||||||
|
handle, address, ref managed, 0, size);
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- EnumerateRemoteSectionsAndModules --
|
||||||
|
private static void EnumSectionsModulesImpl(IntPtr handle,
|
||||||
|
IntPtr sectionCallbackPtr, IntPtr moduleCallbackPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_functions == null) return;
|
||||||
|
|
||||||
|
// Section callback -- forward to native
|
||||||
|
// Native layout (pack=1): RC_Pointer Base(8) + RC_Size Size(8) +
|
||||||
|
// SectionType(4) + SectionCategory(4) + SectionProtection(4) +
|
||||||
|
// char16 Name[16](32) + char16 ModulePath[260](520) = 580 bytes
|
||||||
|
NativeSectionCallback nativeSectionCb = (sectionCallbackPtr != IntPtr.Zero)
|
||||||
|
? Marshal.GetDelegateForFunctionPointer<NativeSectionCallback>(sectionCallbackPtr)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Module callback -- forward to native
|
||||||
|
// Native layout (pack=1): RC_Pointer Base(8) + RC_Size Size(8) +
|
||||||
|
// char16 Path[260](520) = 536 bytes
|
||||||
|
NativeModuleCallback nativeModuleCb = (moduleCallbackPtr != IntPtr.Zero)
|
||||||
|
? Marshal.GetDelegateForFunctionPointer<NativeModuleCallback>(moduleCallbackPtr)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
s_functions.EnumerateRemoteSectionsAndModules(handle,
|
||||||
|
// Section callback
|
||||||
|
(ref ReClassNET.Core.EnumerateRemoteSectionData sdata) =>
|
||||||
|
{
|
||||||
|
if (nativeSectionCb == null) return;
|
||||||
|
|
||||||
|
const int kSize = 8 + 8 + 4 + 4 + 4 + 32 + 520; // 580
|
||||||
|
IntPtr mem = Marshal.AllocHGlobal(kSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] z = new byte[kSize];
|
||||||
|
Marshal.Copy(z, 0, mem, kSize);
|
||||||
|
|
||||||
|
Marshal.WriteInt64(mem, 0, sdata.BaseAddress.ToInt64());
|
||||||
|
Marshal.WriteInt64(mem, 8, sdata.Size.ToInt64());
|
||||||
|
Marshal.WriteInt32(mem, 16, (int)sdata.Type);
|
||||||
|
Marshal.WriteInt32(mem, 20, (int)sdata.Category);
|
||||||
|
Marshal.WriteInt32(mem, 24, (int)sdata.Protection);
|
||||||
|
|
||||||
|
if (sdata.Name != null)
|
||||||
|
{
|
||||||
|
char[] c = sdata.Name.ToCharArray();
|
||||||
|
Marshal.Copy(c, 0, new IntPtr(mem.ToInt64() + 28),
|
||||||
|
Math.Min(c.Length, 15));
|
||||||
|
}
|
||||||
|
if (sdata.ModulePath != null)
|
||||||
|
{
|
||||||
|
char[] c = sdata.ModulePath.ToCharArray();
|
||||||
|
Marshal.Copy(c, 0, new IntPtr(mem.ToInt64() + 60),
|
||||||
|
Math.Min(c.Length, 259));
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeSectionCb(mem);
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(mem); }
|
||||||
|
},
|
||||||
|
// Module callback
|
||||||
|
(ref ReClassNET.Core.EnumerateRemoteModuleData mdata) =>
|
||||||
|
{
|
||||||
|
if (nativeModuleCb == null) return;
|
||||||
|
|
||||||
|
const int kSize = 8 + 8 + 520; // 536
|
||||||
|
IntPtr mem = Marshal.AllocHGlobal(kSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] z = new byte[kSize];
|
||||||
|
Marshal.Copy(z, 0, mem, kSize);
|
||||||
|
|
||||||
|
Marshal.WriteInt64(mem, 0, mdata.BaseAddress.ToInt64());
|
||||||
|
Marshal.WriteInt64(mem, 8, mdata.Size.ToInt64());
|
||||||
|
|
||||||
|
if (mdata.Path != null)
|
||||||
|
{
|
||||||
|
char[] c = mdata.Path.ToCharArray();
|
||||||
|
Marshal.Copy(c, 0, new IntPtr(mem.ToInt64() + 16),
|
||||||
|
Math.Min(c.Length, 259));
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeModuleCb(mem);
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(mem); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- ControlRemoteProcess --
|
||||||
|
private static void ControlProcessImpl(IntPtr handle, int action)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
s_functions?.ControlRemoteProcess(handle,
|
||||||
|
(ReClassNET.Core.ControlRemoteProcessAction)action);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
plugins/RcNetPluginCompatLayer/bridge/RcNetBridge.csproj
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AssemblyName>RcNetBridge</AssemblyName>
|
||||||
|
<RootNamespace>RcNetBridge</RootNamespace>
|
||||||
|
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
124
plugins/RemoteProcessMemory/CMakeLists.txt
Normal 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)
|
||||||
939
plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.cpp
Normal 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.append({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, ®s);
|
||||||
|
ptrace(PTRACE_SINGLESTEP, (pid_t)pid, nullptr, nullptr);
|
||||||
|
waitpid((pid_t)pid, &status, 0);
|
||||||
|
|
||||||
|
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, ®s);
|
||||||
|
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, ®s);
|
||||||
|
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, ®s);
|
||||||
|
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();
|
||||||
|
}
|
||||||
88
plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.h
Normal 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();
|
||||||
612
plugins/RemoteProcessMemory/payload/rcx_payload.cpp
Normal 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 */
|
||||||
115
plugins/RemoteProcessMemory/rcx_rpc_protocol.h
Normal 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
|
||||||
593
plugins/RemoteProcessMemory/tests/test_rpc_client.cpp
Normal 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;
|
||||||
|
}
|
||||||
187
plugins/RemoteProcessMemory/tests/test_rpc_host.cpp
Normal 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;
|
||||||
|
}
|
||||||
34
plugins/WinDbgMemory/CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(WinDbgMemoryPlugin 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 ON)
|
||||||
|
|
||||||
|
# Plugin sources
|
||||||
|
set(PLUGIN_SOURCES
|
||||||
|
WinDbgMemoryPlugin.h
|
||||||
|
WinDbgMemoryPlugin.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create shared library (DLL)
|
||||||
|
add_library(WinDbgMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||||
|
|
||||||
|
# Link Qt + DbgEng
|
||||||
|
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets dbgeng ole32)
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
target_include_directories(WinDbgMemoryPlugin PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output to Plugins folder
|
||||||
|
set_target_properties(WinDbgMemoryPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
)
|
||||||
608
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
#include "WinDbgMemoryPlugin.h"
|
||||||
|
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <dbgeng.h>
|
||||||
|
#pragma comment(lib, "dbgeng.lib")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
// Thread dispatch helper
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
template<typename Fn>
|
||||||
|
void WinDbgMemoryProvider::dispatchToOwner(Fn&& fn) const
|
||||||
|
{
|
||||||
|
if (!m_dispatcher) { fn(); return; }
|
||||||
|
|
||||||
|
if (QThread::currentThread() == m_dispatcher->thread()) {
|
||||||
|
// Already on the owning thread — call directly
|
||||||
|
fn();
|
||||||
|
} else {
|
||||||
|
// Marshal to the owning thread and block until done
|
||||||
|
QMetaObject::invokeMethod(m_dispatcher, std::forward<Fn>(fn),
|
||||||
|
Qt::BlockingQueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
// WinDbgMemoryProvider implementation
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
||||||
|
{
|
||||||
|
// Create a dedicated thread for all DbgEng COM operations.
|
||||||
|
// DbgEng's remote transport (TCP/named-pipe) is thread-affine — all
|
||||||
|
// calls must happen on the thread that called DebugConnect/DebugCreate.
|
||||||
|
// A private thread with its own event loop guarantees:
|
||||||
|
// 1. dispatchToOwner() works from any calling thread (main, thread-pool, etc.)
|
||||||
|
// 2. No deadlock — the DbgEng thread is never blocked by the caller
|
||||||
|
m_dbgThread = new QThread();
|
||||||
|
m_dbgThread->setObjectName(QStringLiteral("DbgEngThread"));
|
||||||
|
m_dbgThread->start();
|
||||||
|
|
||||||
|
m_dispatcher = new DbgEngDispatcher();
|
||||||
|
m_dispatcher->moveToThread(m_dbgThread);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Run all DbgEng initialization on the dedicated thread.
|
||||||
|
// BlockingQueuedConnection blocks us until the lambda finishes,
|
||||||
|
// so member variables written inside are visible after the call.
|
||||||
|
dispatchToOwner([this, &target]() {
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
qDebug() << "[WinDbg] Opening target:" << target
|
||||||
|
<< "on DbgEng thread" << QThread::currentThread();
|
||||||
|
|
||||||
|
if (target.startsWith("tcp:", Qt::CaseInsensitive)
|
||||||
|
|| target.startsWith("npipe:", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
// ── Remote: connect to existing WinDbg debug server ──
|
||||||
|
QByteArray connUtf8 = target.toUtf8();
|
||||||
|
qDebug() << "[WinDbg] DebugConnect:" << target;
|
||||||
|
hr = DebugConnect(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) {
|
||||||
|
qWarning() << "[WinDbg] DebugConnect FAILED hr=0x" << Qt::hex << (unsigned long)hr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_isRemote = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ── Local: create debug client for pid/dump ──
|
||||||
|
hr = DebugCreate(IID_IDebugClient, (void**)&m_client);
|
||||||
|
qDebug() << "[WinDbg] DebugCreate hr=" << Qt::hex << (unsigned long)hr
|
||||||
|
<< "client=" << (void*)m_client;
|
||||||
|
if (FAILED(hr) || !m_client) {
|
||||||
|
qWarning() << "[WinDbg] DebugCreate FAILED hr=0x" << Qt::hex << (unsigned long)hr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.startsWith("pid:", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
ULONG pid = target.mid(4).trimmed().toULong(&ok);
|
||||||
|
if (!ok || pid == 0) {
|
||||||
|
qWarning() << "[WinDbg] Invalid PID in target:" << target;
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "[WinDbg] Attaching to PID" << pid << "(non-invasive)";
|
||||||
|
hr = m_client->AttachProcess(
|
||||||
|
0, pid,
|
||||||
|
DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND);
|
||||||
|
qDebug() << "[WinDbg] AttachProcess hr=" << Qt::hex << (unsigned long)hr;
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "[WinDbg] AttachProcess FAILED";
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (target.startsWith("dump:", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
QString path = target.mid(5).trimmed();
|
||||||
|
QByteArray pathUtf8 = path.toUtf8();
|
||||||
|
|
||||||
|
qDebug() << "[WinDbg] Opening dump file:" << path;
|
||||||
|
hr = m_client->OpenDumpFile(pathUtf8.constData());
|
||||||
|
qDebug() << "[WinDbg] OpenDumpFile hr=" << Qt::hex << (unsigned long)hr;
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "[WinDbg] OpenDumpFile FAILED";
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "[WinDbg] Unknown target format:" << target;
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initInterfaces();
|
||||||
|
|
||||||
|
// WaitForEvent to finalize the attach/dump load.
|
||||||
|
// For remote connections the server session is already active — skip.
|
||||||
|
if (m_control && !m_isRemote) {
|
||||||
|
qDebug() << "[WinDbg] WaitForEvent...";
|
||||||
|
hr = m_control->WaitForEvent(0, 10000);
|
||||||
|
qDebug() << "[WinDbg] WaitForEvent hr=" << Qt::hex << (unsigned long)hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
querySessionInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
#else
|
||||||
|
Q_UNUSED(target);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinDbgMemoryProvider::initInterfaces()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!m_client) return;
|
||||||
|
|
||||||
|
HRESULT hr;
|
||||||
|
hr = m_client->QueryInterface(IID_IDebugDataSpaces, (void**)&m_dataSpaces);
|
||||||
|
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;
|
||||||
|
|
||||||
|
hr = m_client->QueryInterface(IID_IDebugSymbols, (void**)&m_symbols);
|
||||||
|
qDebug() << "[WinDbg] IDebugSymbols hr=" << Qt::hex << (unsigned long)hr
|
||||||
|
<< "ptr=" << (void*)m_symbols;
|
||||||
|
|
||||||
|
if (!m_dataSpaces) {
|
||||||
|
qWarning() << "[WinDbg] No IDebugDataSpaces — cleaning up";
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinDbgMemoryProvider::querySessionInfo()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!m_client) return;
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
if (m_control) {
|
||||||
|
ULONG debugClass = 0, debugQualifier = 0;
|
||||||
|
hr = m_control->GetDebuggeeType(&debugClass, &debugQualifier);
|
||||||
|
qDebug() << "[WinDbg] GetDebuggeeType hr=" << Qt::hex << (unsigned long)hr
|
||||||
|
<< "class=" << debugClass << "qualifier=" << debugQualifier;
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
m_isLive = (debugQualifier < DEBUG_DUMP_SMALL);
|
||||||
|
m_writable = m_isLive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
<< "isLive=" << m_isLive;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
WinDbgMemoryProvider::~WinDbgMemoryProvider()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Dispatch COM cleanup to the DbgEng thread (thread-affine release)
|
||||||
|
if (m_dbgThread && m_dbgThread->isRunning() && m_dispatcher) {
|
||||||
|
dispatchToOwner([this]() {
|
||||||
|
if (m_client) {
|
||||||
|
if (m_isRemote)
|
||||||
|
m_client->EndSession(DEBUG_END_DISCONNECT);
|
||||||
|
else
|
||||||
|
m_client->DetachProcesses();
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Thread not running — clean up directly (best-effort)
|
||||||
|
if (m_client) {
|
||||||
|
if (m_isRemote)
|
||||||
|
m_client->EndSession(DEBUG_END_DISCONNECT);
|
||||||
|
else
|
||||||
|
m_client->DetachProcesses();
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
cleanup();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Stop the dedicated thread
|
||||||
|
if (m_dbgThread) {
|
||||||
|
m_dbgThread->quit();
|
||||||
|
m_dbgThread->wait(3000);
|
||||||
|
delete m_dbgThread;
|
||||||
|
m_dbgThread = nullptr;
|
||||||
|
}
|
||||||
|
delete m_dispatcher;
|
||||||
|
m_dispatcher = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinDbgMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!m_dataSpaces || len <= 0) return false;
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
dispatchToOwner([&]() {
|
||||||
|
ULONG bytesRead = 0;
|
||||||
|
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;
|
||||||
|
#else
|
||||||
|
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinDbgMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!m_dataSpaces || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
dispatchToOwner([&]() {
|
||||||
|
ULONG bytesWritten = 0;
|
||||||
|
HRESULT hr = m_dataSpaces->WriteVirtual(addr, const_cast<void*>(buf),
|
||||||
|
(ULONG)len, &bytesWritten);
|
||||||
|
result = SUCCEEDED(hr) && bytesWritten == (ULONG)len;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int WinDbgMemoryProvider::size() const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return m_dataSpaces ? 0x10000 : 0;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinDbgMemoryProvider::isReadable(uint64_t /*addr*/, int len) const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
// DbgEng's ReadVirtual can read any mapped virtual address.
|
||||||
|
return m_dataSpaces != nullptr && len >= 0;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!m_symbols) return {};
|
||||||
|
|
||||||
|
QString result;
|
||||||
|
dispatchToOwner([&]() {
|
||||||
|
char nameBuf[512] = {};
|
||||||
|
ULONG nameSize = 0;
|
||||||
|
ULONG64 displacement = 0;
|
||||||
|
HRESULT hr = m_symbols->GetNameByOffset(addr, nameBuf, sizeof(nameBuf),
|
||||||
|
&nameSize, &displacement);
|
||||||
|
if (SUCCEEDED(hr) && nameSize > 0) {
|
||||||
|
result = QString::fromUtf8(nameBuf);
|
||||||
|
if (displacement > 0)
|
||||||
|
result += QStringLiteral("+0x%1").arg(displacement, 0, 16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
Q_UNUSED(addr);
|
||||||
|
return {};
|
||||||
|
#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, ¶ms)))
|
||||||
|
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
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
QIcon WinDbgMemoryPlugin::Icon() const
|
||||||
|
{
|
||||||
|
return qApp->style()->standardIcon(QStyle::SP_DriveNetIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinDbgMemoryPlugin::canHandle(const QString& target) const
|
||||||
|
{
|
||||||
|
return target.startsWith("tcp:", Qt::CaseInsensitive)
|
||||||
|
|| target.startsWith("npipe:", Qt::CaseInsensitive)
|
||||||
|
|| target.startsWith("pid:", Qt::CaseInsensitive)
|
||||||
|
|| target.startsWith("dump:", Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<rcx::Provider> WinDbgMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
|
||||||
|
{
|
||||||
|
auto provider = std::make_unique<WinDbgMemoryProvider>(target);
|
||||||
|
if (!provider->isValid())
|
||||||
|
{
|
||||||
|
if (errorMsg) {
|
||||||
|
if (target.startsWith("tcp:", Qt::CaseInsensitive)
|
||||||
|
|| target.startsWith("npipe:", Qt::CaseInsensitive))
|
||||||
|
*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.")
|
||||||
|
.arg(target);
|
||||||
|
else if (target.startsWith("pid:", Qt::CaseInsensitive))
|
||||||
|
*errorMsg = QString("Failed to attach to process.\n\n"
|
||||||
|
"Target: %1\n\n"
|
||||||
|
"Make sure the process is running and you have "
|
||||||
|
"sufficient privileges (try Run as Administrator).")
|
||||||
|
.arg(target);
|
||||||
|
else
|
||||||
|
*errorMsg = QString("Failed to open dump file.\n\n"
|
||||||
|
"Target: %1\n\n"
|
||||||
|
"Make sure the file exists and is a valid dump.")
|
||||||
|
.arg(target);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t WinDbgMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(target);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||||
|
{
|
||||||
|
QDialog dlg(parent);
|
||||||
|
dlg.setWindowTitle("WinDbg Settings");
|
||||||
|
dlg.resize(460, 300);
|
||||||
|
|
||||||
|
QPalette dlgPal = qApp->palette();
|
||||||
|
dlg.setPalette(dlgPal);
|
||||||
|
dlg.setAutoFillBackground(true);
|
||||||
|
|
||||||
|
auto* layout = new QVBoxLayout(&dlg);
|
||||||
|
|
||||||
|
layout->addWidget(new QLabel(
|
||||||
|
"Connect to a running WinDbg debug server.\n"
|
||||||
|
"In WinDbg, run: .server tcp:port=5055\n\n"
|
||||||
|
"Non-invasive debug and dump files only.\n"
|
||||||
|
"Execution control (bp, g, t, p) is not supported."));
|
||||||
|
|
||||||
|
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");
|
||||||
|
layout->addWidget(connEdit);
|
||||||
|
|
||||||
|
layout->addSpacing(4);
|
||||||
|
layout->addWidget(new QLabel("Run one of these in WinDbg first:"));
|
||||||
|
|
||||||
|
auto addExample = [&](const QString& text) {
|
||||||
|
auto* row = new QHBoxLayout;
|
||||||
|
auto* label = new QLabel(text);
|
||||||
|
QPalette lp = dlgPal;
|
||||||
|
lp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||||
|
label->setPalette(lp);
|
||||||
|
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
row->addWidget(label, 1);
|
||||||
|
auto* copyBtn = new QPushButton("Copy");
|
||||||
|
copyBtn->setFixedWidth(50);
|
||||||
|
copyBtn->setToolTip("Copy to clipboard");
|
||||||
|
QObject::connect(copyBtn, &QPushButton::clicked, [text]() {
|
||||||
|
QGuiApplication::clipboard()->setText(text);
|
||||||
|
});
|
||||||
|
row->addWidget(copyBtn);
|
||||||
|
layout->addLayout(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
addExample(".server tcp:port=5055");
|
||||||
|
addExample(".server npipe:pipe=reclass");
|
||||||
|
layout->addStretch();
|
||||||
|
|
||||||
|
auto* btnLayout = new QHBoxLayout;
|
||||||
|
btnLayout->addStretch();
|
||||||
|
auto* okBtn = new QPushButton("OK");
|
||||||
|
auto* cancelBtn = new QPushButton("Cancel");
|
||||||
|
btnLayout->addWidget(okBtn);
|
||||||
|
btnLayout->addWidget(cancelBtn);
|
||||||
|
layout->addLayout(btnLayout);
|
||||||
|
|
||||||
|
QObject::connect(okBtn, &QPushButton::clicked, &dlg, &QDialog::accept);
|
||||||
|
QObject::connect(cancelBtn, &QPushButton::clicked, &dlg, &QDialog::reject);
|
||||||
|
|
||||||
|
if (dlg.exec() != QDialog::Accepted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QString conn = connEdit->text().trimmed();
|
||||||
|
if (conn.isEmpty()) return false;
|
||||||
|
*target = conn;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
// Plugin factory
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||||
|
{
|
||||||
|
return new WinDbgMemoryPlugin();
|
||||||
|
}
|
||||||
127
plugins/WinDbgMemory/WinDbgMemoryPlugin.h
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../../src/iplugin.h"
|
||||||
|
#include "../../src/core.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
// Forward declarations for DbgEng COM interfaces
|
||||||
|
struct IDebugClient;
|
||||||
|
struct IDebugDataSpaces;
|
||||||
|
struct IDebugDataSpaces2;
|
||||||
|
struct IDebugControl;
|
||||||
|
struct IDebugSymbols;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WinDbg memory provider
|
||||||
|
*
|
||||||
|
* Uses DbgEng to read memory from:
|
||||||
|
* - An existing WinDbg debug server via DebugConnect (tcp/npipe)
|
||||||
|
* - A live process by PID via DebugCreate (non-invasive attach)
|
||||||
|
* - A crash dump (.dmp) file via DebugCreate
|
||||||
|
*
|
||||||
|
* Target string format:
|
||||||
|
* "tcp:Port=5055,Server=localhost" - connect to WinDbg debug server (TCP)
|
||||||
|
* "npipe:Pipe=name,Server=localhost" - connect to WinDbg debug server (named pipe)
|
||||||
|
* "pid:1234" - attach to process 1234
|
||||||
|
* "dump:C:/path/to/file.dmp" - open dump file
|
||||||
|
*
|
||||||
|
* Threading: All DbgEng COM calls are dispatched to the thread that created
|
||||||
|
* the connection (DebugConnect/DebugCreate). This is required because the
|
||||||
|
* remote transport (TCP/named-pipe) binds to the creating thread. The
|
||||||
|
* controller's background refresh threads call read() which transparently
|
||||||
|
* marshals to the owning thread via BlockingQueuedConnection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Helper QObject that lives on the DbgEng-owning thread.
|
||||||
|
// Used as a target for QMetaObject::invokeMethod to marshal calls.
|
||||||
|
class DbgEngDispatcher : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using QObject::QObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WinDbgMemoryProvider : public rcx::Provider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Create a provider from a target string
|
||||||
|
WinDbgMemoryProvider(const QString& target);
|
||||||
|
~WinDbgMemoryProvider() override;
|
||||||
|
|
||||||
|
// Required overrides
|
||||||
|
bool read(uint64_t addr, void* buf, int len) const override;
|
||||||
|
int size() const override;
|
||||||
|
|
||||||
|
// Optional overrides
|
||||||
|
bool isReadable(uint64_t addr, int len) const override;
|
||||||
|
bool write(uint64_t addr, const void* buf, int len) override;
|
||||||
|
bool isWritable() const override { return m_writable; }
|
||||||
|
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; }
|
||||||
|
int pointerSize() const override { return m_pointerSize; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initInterfaces(); // get IDebugDataSpaces/Control/Symbols from client
|
||||||
|
void querySessionInfo(); // determine live/dump, writable, name, base
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
// Marshal a lambda to the DbgEng-owning thread. If already on that
|
||||||
|
// thread, calls directly. Otherwise blocks via QueuedConnection.
|
||||||
|
template<typename Fn>
|
||||||
|
void dispatchToOwner(Fn&& fn) const;
|
||||||
|
|
||||||
|
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
|
||||||
|
// that called DebugConnect. A private thread with its own event loop
|
||||||
|
// ensures dispatchToOwner() works from any calling thread (including
|
||||||
|
// QtConcurrent workers and the main/GUI thread) without deadlock.
|
||||||
|
QThread* m_dbgThread = nullptr;
|
||||||
|
DbgEngDispatcher* m_dispatcher = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin that provides WinDbgMemoryProvider
|
||||||
|
*
|
||||||
|
* Uses DbgEng to read memory via:
|
||||||
|
* - Remote connection to an existing WinDbg debug server (tcp/npipe)
|
||||||
|
* - Local non-invasive attach to a live process (pid)
|
||||||
|
* - Local crash dump file (dump)
|
||||||
|
*/
|
||||||
|
class WinDbgMemoryPlugin : public IProviderPlugin
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string Name() const override { return "WinDbg Memory"; }
|
||||||
|
std::string Version() const override { return "2.0.0"; }
|
||||||
|
std::string Author() const override { return "Reclass"; }
|
||||||
|
std::string Description() const override { return "Read memory via DbgEng (live process attach or crash dump)"; }
|
||||||
|
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plugin export
|
||||||
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||||
BIN
screenshot.png
|
Before Width: | Height: | Size: 74 KiB |
@@ -1,4 +1,4 @@
|
|||||||
# PowerShell script to build ReclassX
|
# PowerShell script to build Reclass
|
||||||
# Automatically detects Qt installation and configures build environment
|
# Automatically detects Qt installation and configures build environment
|
||||||
|
|
||||||
#Requires -Version 5.1
|
#Requires -Version 5.1
|
||||||
@@ -303,7 +303,7 @@ function Find-MinGWDirectory {
|
|||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Write-ColorOutput "`n========================================" Cyan
|
Write-ColorOutput "`n========================================" Cyan
|
||||||
Write-ColorOutput "ReclassX Build Script" Cyan
|
Write-ColorOutput "Reclass Build Script" Cyan
|
||||||
Write-ColorOutput "========================================`n" Cyan
|
Write-ColorOutput "========================================`n" Cyan
|
||||||
|
|
||||||
# Get script directory and project root
|
# Get script directory and project root
|
||||||
@@ -426,7 +426,7 @@ try {
|
|||||||
Write-ColorOutput "`nCMake configuration completed successfully.`n" Green
|
Write-ColorOutput "`nCMake configuration completed successfully.`n" Green
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
Write-ColorOutput "Building ReclassX..." Cyan
|
Write-ColorOutput "Building Reclass..." Cyan
|
||||||
|
|
||||||
$cores = (Get-CimInstance -ClassName Win32_Processor).NumberOfLogicalProcessors
|
$cores = (Get-CimInstance -ClassName Win32_Processor).NumberOfLogicalProcessors
|
||||||
if (-not $cores -or $cores -lt 1) {
|
if (-not $cores -or $cores -lt 1) {
|
||||||
@@ -445,8 +445,8 @@ try {
|
|||||||
# Find executable
|
# Find executable
|
||||||
Write-ColorOutput "Locating executable..." Cyan
|
Write-ColorOutput "Locating executable..." Cyan
|
||||||
$exePaths = @(
|
$exePaths = @(
|
||||||
(Join-Path $buildDir "ReclassX.exe"),
|
(Join-Path $buildDir "Reclass.exe"),
|
||||||
(Join-Path $buildDir "$BuildType\ReclassX.exe")
|
(Join-Path $buildDir "$BuildType\Reclass.exe")
|
||||||
)
|
)
|
||||||
|
|
||||||
$exePath = $null
|
$exePath = $null
|
||||||
@@ -477,7 +477,7 @@ try {
|
|||||||
|
|
||||||
# Count deployed files
|
# Count deployed files
|
||||||
$deployedFiles = Get-ChildItem -Path $exeDir -Recurse -File | Where-Object {
|
$deployedFiles = Get-ChildItem -Path $exeDir -Recurse -File | Where-Object {
|
||||||
$_.Name -ne "ReclassX.exe" -and $_.Extension -match '\.(dll|qm)$'
|
$_.Name -ne "Reclass.exe" -and $_.Extension -match '\.(dll|qm)$'
|
||||||
}
|
}
|
||||||
if ($deployedFiles) {
|
if ($deployedFiles) {
|
||||||
Write-ColorOutput "Deployed $($deployedFiles.Count) Qt dependency files." Gray
|
Write-ColorOutput "Deployed $($deployedFiles.Count) Qt dependency files." Gray
|
||||||
@@ -491,7 +491,7 @@ try {
|
|||||||
Write-ColorOutput "Application may not run without Qt DLLs in PATH" Yellow
|
Write-ColorOutput "Application may not run without Qt DLLs in PATH" Yellow
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-ColorOutput "WARNING: Could not locate ReclassX.exe" Yellow
|
Write-ColorOutput "WARNING: Could not locate Reclass.exe" Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
@@ -507,5 +507,5 @@ Write-ColorOutput "========================================`n" Cyan
|
|||||||
|
|
||||||
if ($exePath) {
|
if ($exePath) {
|
||||||
Write-ColorOutput "Run the application with:" White
|
Write-ColorOutput "Run the application with:" White
|
||||||
Write-ColorOutput " .\build\ReclassX.exe`n" Cyan
|
Write-ColorOutput " .\build\Reclass.exe`n" Cyan
|
||||||
}
|
}
|
||||||
|
|||||||
168
scripts/build_macos.sh
Executable 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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# PowerShell script to build QScintilla static library for ReclassX
|
# PowerShell script to build QScintilla static library for Reclass
|
||||||
# This script checks for Qt installation, prompts if missing, and builds QScintilla
|
# This script checks for Qt installation, prompts if missing, and builds QScintilla
|
||||||
|
|
||||||
#Requires -Version 5.1
|
#Requires -Version 5.1
|
||||||
@@ -272,7 +272,7 @@ function Find-MakeCommand {
|
|||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Write-ColorOutput "`n========================================" Cyan
|
Write-ColorOutput "`n========================================" Cyan
|
||||||
Write-ColorOutput "QScintilla Build Script for ReclassX" Cyan
|
Write-ColorOutput "QScintilla Build Script for Reclass" Cyan
|
||||||
Write-ColorOutput "========================================`n" Cyan
|
Write-ColorOutput "========================================`n" Cyan
|
||||||
|
|
||||||
# Get script directory and project root
|
# Get script directory and project root
|
||||||
@@ -423,7 +423,7 @@ try {
|
|||||||
Write-Host " - $($lib.Name) ($sizeMB MB)" -ForegroundColor Green
|
Write-Host " - $($lib.Name) ($sizeMB MB)" -ForegroundColor Green
|
||||||
Write-Host " Path: $($lib.Path)" -ForegroundColor Gray
|
Write-Host " Path: $($lib.Path)" -ForegroundColor Gray
|
||||||
}
|
}
|
||||||
Write-ColorOutput "`nYou can now build ReclassX with CMake." Green
|
Write-ColorOutput "`nYou can now build Reclass with CMake." Green
|
||||||
} else {
|
} else {
|
||||||
Write-ColorOutput "`nWARNING: Build completed but no library files found." Yellow
|
Write-ColorOutput "`nWARNING: Build completed but no library files found." Yellow
|
||||||
Write-ColorOutput "Expected files: qscintilla2_qt6.a or qscintilla2_qt6.lib" Yellow
|
Write-ColorOutput "Expected files: qscintilla2_qt6.a or qscintilla2_qt6.lib" Yellow
|
||||||
|
|||||||
439
src/addressparser.cpp
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
#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.
|
||||||
|
// 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.
|
||||||
|
bool parseIdentifierOrHex(uint64_t& result) {
|
||||||
|
int start = m_pos;
|
||||||
|
bool hasNonHex = false;
|
||||||
|
|
||||||
|
// Scan full token
|
||||||
|
while (!atEnd() && isIdentChar(peek())) {
|
||||||
|
if (!isHexDigit(peek()))
|
||||||
|
hasNonHex = true;
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// '[' 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
|
||||||
28
src/addressparser.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddressParser {
|
||||||
|
public:
|
||||||
|
static AddressParseResult evaluate(const QString& formula, int ptrSize = 8,
|
||||||
|
const AddressParserCallbacks* cb = nullptr);
|
||||||
|
static QString validate(const QString& formula);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
1
src/app.rc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
IDI_ICON1 ICON "icons/class.ico"
|
||||||
778
src/compose.cpp
2890
src/controller.cpp
@@ -7,13 +7,15 @@
|
|||||||
#include <QUndoCommand>
|
#include <QUndoCommand>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
|
#include <QPointer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class QSplitter;
|
|
||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
|
|
||||||
class RcxController;
|
class RcxController;
|
||||||
|
class TypeSelectorPopup;
|
||||||
|
struct TypeEntry;
|
||||||
|
enum class TypePopupMode;
|
||||||
|
|
||||||
// ── Document ──
|
// ── Document ──
|
||||||
|
|
||||||
@@ -38,7 +40,8 @@ public:
|
|||||||
return m ? QString::fromLatin1(m->typeName) : QStringLiteral("???");
|
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) const;
|
||||||
bool save(const QString& path);
|
bool save(const QString& path);
|
||||||
bool load(const QString& path);
|
bool load(const QString& path);
|
||||||
void loadData(const QString& binaryPath);
|
void loadData(const QString& binaryPath);
|
||||||
@@ -63,12 +66,12 @@ private:
|
|||||||
// ── Saved source entry ──
|
// ── Saved source entry ──
|
||||||
|
|
||||||
struct SavedSourceEntry {
|
struct SavedSourceEntry {
|
||||||
QString kind; // "File" or "Process"
|
QString kind; // "File" or provider identifier (e.g. "processmemory")
|
||||||
QString displayName; // filename or process name
|
QString displayName; // filename or process name
|
||||||
QString filePath; // for File sources
|
QString filePath; // for File sources
|
||||||
uint32_t pid = 0; // for Process sources
|
QString providerTarget; // for plugin providers (e.g. "pid:name")
|
||||||
QString processName; // for Process sources
|
|
||||||
uint64_t baseAddress = 0;
|
uint64_t baseAddress = 0;
|
||||||
|
QString baseAddressFormula;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Controller ──
|
// ── Controller ──
|
||||||
@@ -80,23 +83,36 @@ public:
|
|||||||
~RcxController() override;
|
~RcxController() override;
|
||||||
|
|
||||||
RcxEditor* primaryEditor() const;
|
RcxEditor* primaryEditor() const;
|
||||||
RcxEditor* addSplitEditor(QSplitter* splitter);
|
RcxEditor* addSplitEditor(QWidget* parent = nullptr);
|
||||||
void removeSplitEditor(RcxEditor* editor);
|
void removeSplitEditor(RcxEditor* editor);
|
||||||
QList<RcxEditor*> editors() const { return m_editors; }
|
QList<RcxEditor*> editors() const { return m_editors; }
|
||||||
|
|
||||||
|
void convertRootKeyword(const QString& newKeyword);
|
||||||
void changeNodeKind(int nodeIdx, NodeKind newKind);
|
void changeNodeKind(int nodeIdx, NodeKind newKind);
|
||||||
void renameNode(int nodeIdx, const QString& newName);
|
void renameNode(int nodeIdx, const QString& newName);
|
||||||
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
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 removeNode(int nodeIdx);
|
||||||
void toggleCollapse(int nodeIdx);
|
void toggleCollapse(int nodeIdx);
|
||||||
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
|
void materializeRefChildren(int nodeIdx);
|
||||||
|
void setNodeValue(int nodeIdx, int subLine, const QString& text,
|
||||||
|
bool isAscii = false, uint64_t resolvedAddr = 0);
|
||||||
void duplicateNode(int nodeIdx);
|
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 showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);
|
||||||
void batchRemoveNodes(const QVector<int>& nodeIndices);
|
void batchRemoveNodes(const QVector<int>& nodeIndices);
|
||||||
void batchChangeKind(const QVector<int>& nodeIndices, NodeKind newKind);
|
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 applyCommand(const Command& cmd, bool isUndo);
|
||||||
void refresh();
|
void refresh();
|
||||||
|
void applyTypePopupResult(TypePopupMode mode, int nodeIdx, const TypeEntry& entry, const QString& fullText);
|
||||||
|
uint64_t findOrCreateStructByName(const QString& typeName);
|
||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
void handleNodeClick(RcxEditor* source, int line, uint64_t nodeId,
|
void handleNodeClick(RcxEditor* source, int line, uint64_t nodeId,
|
||||||
@@ -111,6 +127,33 @@ public:
|
|||||||
|
|
||||||
RcxDocument* document() const { return m_doc; }
|
RcxDocument* document() const { return m_doc; }
|
||||||
void setEditorFont(const QString& fontName);
|
void setEditorFont(const QString& fontName);
|
||||||
|
void setRefreshInterval(int ms);
|
||||||
|
void setCompactColumns(bool v);
|
||||||
|
void setTreeLines(bool v);
|
||||||
|
void resetProvider();
|
||||||
|
|
||||||
|
// MCP bridge accessors
|
||||||
|
void setSuppressRefresh(bool v) { m_suppressRefresh = v; }
|
||||||
|
void attachViaPlugin(const QString& providerIdentifier, const QString& target);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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:
|
signals:
|
||||||
void nodeSelected(int nodeIdx);
|
void nodeSelected(int nodeIdx);
|
||||||
@@ -123,29 +166,41 @@ private:
|
|||||||
QSet<uint64_t> m_selIds;
|
QSet<uint64_t> m_selIds;
|
||||||
int m_anchorLine = -1;
|
int m_anchorLine = -1;
|
||||||
bool m_suppressRefresh = false;
|
bool m_suppressRefresh = false;
|
||||||
|
bool m_compactColumns = false;
|
||||||
|
bool m_treeLines = false;
|
||||||
uint64_t m_viewRootId = 0;
|
uint64_t m_viewRootId = 0;
|
||||||
|
|
||||||
// ── Saved sources for quick-switch ──
|
// ── Saved sources for quick-switch ──
|
||||||
QVector<SavedSourceEntry> m_savedSources;
|
QVector<SavedSourceEntry> m_savedSources;
|
||||||
int m_activeSourceIdx = -1;
|
int m_activeSourceIdx = -1;
|
||||||
|
|
||||||
|
// ── 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 ──
|
// ── Auto-refresh state ──
|
||||||
|
using PageMap = QHash<uint64_t, QByteArray>;
|
||||||
QTimer* m_refreshTimer = nullptr;
|
QTimer* m_refreshTimer = nullptr;
|
||||||
QFutureWatcher<QByteArray>* m_refreshWatcher = nullptr;
|
QFutureWatcher<PageMap>* m_refreshWatcher = nullptr;
|
||||||
std::unique_ptr<SnapshotProvider> m_snapshotProv;
|
std::unique_ptr<SnapshotProvider> m_snapshotProv;
|
||||||
QByteArray m_prevSnapshot;
|
PageMap m_prevPages;
|
||||||
QSet<int64_t> m_changedOffsets;
|
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_refreshGen = 0;
|
||||||
uint64_t m_readGen = 0;
|
uint64_t m_readGen = 0;
|
||||||
bool m_readInFlight = false;
|
bool m_readInFlight = false;
|
||||||
|
|
||||||
|
QVector<RcxDocument*>* m_projectDocs = nullptr;
|
||||||
|
|
||||||
void connectEditor(RcxEditor* editor);
|
void connectEditor(RcxEditor* editor);
|
||||||
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
||||||
void updateCommandRow();
|
void updateCommandRow();
|
||||||
void performRealignment(uint64_t structId, int targetAlign);
|
|
||||||
void attachToProcess(uint32_t pid, const QString& processName);
|
|
||||||
void switchToSavedSource(int idx);
|
void switchToSavedSource(int idx);
|
||||||
void pushSavedSourcesToEditors();
|
void pushSavedSourcesToEditors();
|
||||||
|
void showTypePopup(RcxEditor* editor, TypePopupMode mode, int nodeIdx, QPoint globalPos);
|
||||||
|
TypeSelectorPopup* ensurePopup(RcxEditor* editor);
|
||||||
|
|
||||||
// ── Auto-refresh methods ──
|
// ── Auto-refresh methods ──
|
||||||
void setupAutoRefresh();
|
void setupAutoRefresh();
|
||||||
@@ -153,6 +208,10 @@ private:
|
|||||||
void onReadComplete();
|
void onReadComplete();
|
||||||
int computeDataExtent() const;
|
int computeDataExtent() const;
|
||||||
void resetSnapshot();
|
void resetSnapshot();
|
||||||
|
void collectPointerRanges(uint64_t structId, uint64_t memBase,
|
||||||
|
int depth, int maxDepth,
|
||||||
|
QSet<QPair<uint64_t,uint64_t>>& visited,
|
||||||
|
QVector<QPair<uint64_t,int>>& ranges) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
436
src/core.h
@@ -8,8 +8,10 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
#include "providers/provider.h"
|
#include "providers/provider.h"
|
||||||
#include "providers/buffer_provider.h"
|
#include "providers/buffer_provider.h"
|
||||||
@@ -25,17 +27,23 @@ enum class NodeKind : uint8_t {
|
|||||||
UInt8, UInt16, UInt32, UInt64,
|
UInt8, UInt16, UInt32, UInt64,
|
||||||
Float, Double, Bool,
|
Float, Double, Bool,
|
||||||
Pointer32, Pointer64,
|
Pointer32, Pointer64,
|
||||||
|
FuncPtr32, FuncPtr64,
|
||||||
Vec2, Vec3, Vec4, Mat4x4,
|
Vec2, Vec3, Vec4, Mat4x4,
|
||||||
UTF8, UTF16,
|
UTF8, UTF16,
|
||||||
Padding,
|
|
||||||
Struct, Array
|
Struct, Array
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Kind flags (replaces repeated Hex/Padding switches) ──
|
} // namespace rcx (temporarily close for qHash)
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
inline uint qHash(rcx::NodeKind key, uint seed = 0) { return qHash(static_cast<int>(key), seed); }
|
||||||
|
#endif
|
||||||
|
namespace rcx { // reopen
|
||||||
|
|
||||||
|
// ── Kind flags (replaces repeated Hex switches) ──
|
||||||
|
|
||||||
enum KindFlags : uint32_t {
|
enum KindFlags : uint32_t {
|
||||||
KF_None = 0,
|
KF_None = 0,
|
||||||
KF_HexPreview = 1 << 0, // Hex8..Hex64 + Padding (ASCII+hex layout)
|
KF_HexPreview = 1 << 0, // Hex8..Hex64 (ASCII+hex layout)
|
||||||
KF_Container = 1 << 1, // Struct/Array
|
KF_Container = 1 << 1, // Struct/Array
|
||||||
KF_String = 1 << 2, // UTF8/UTF16
|
KF_String = 1 << 2, // UTF8/UTF16
|
||||||
KF_Vector = 1 << 3, // Vec2/3/4
|
KF_Vector = 1 << 3, // Vec2/3/4
|
||||||
@@ -72,13 +80,14 @@ inline constexpr KindMeta kKindMeta[] = {
|
|||||||
{NodeKind::Bool, "Bool", "bool", 1, 1, 1, KF_None},
|
{NodeKind::Bool, "Bool", "bool", 1, 1, 1, KF_None},
|
||||||
{NodeKind::Pointer32, "Pointer32", "ptr32", 4, 1, 4, KF_None},
|
{NodeKind::Pointer32, "Pointer32", "ptr32", 4, 1, 4, KF_None},
|
||||||
{NodeKind::Pointer64, "Pointer64", "ptr64", 8, 1, 8, KF_None},
|
{NodeKind::Pointer64, "Pointer64", "ptr64", 8, 1, 8, KF_None},
|
||||||
|
{NodeKind::FuncPtr32, "FuncPtr32", "fnptr32", 4, 1, 4, KF_None},
|
||||||
|
{NodeKind::FuncPtr64, "FuncPtr64", "fnptr64", 8, 1, 8, KF_None},
|
||||||
{NodeKind::Vec2, "Vec2", "vec2", 8, 1, 4, KF_Vector},
|
{NodeKind::Vec2, "Vec2", "vec2", 8, 1, 4, KF_Vector},
|
||||||
{NodeKind::Vec3, "Vec3", "vec3", 12, 1, 4, KF_Vector},
|
{NodeKind::Vec3, "Vec3", "vec3", 12, 1, 4, KF_Vector},
|
||||||
{NodeKind::Vec4, "Vec4", "vec4", 16, 1, 4, KF_Vector},
|
{NodeKind::Vec4, "Vec4", "vec4", 16, 1, 4, KF_Vector},
|
||||||
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
|
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
|
||||||
{NodeKind::UTF8, "UTF8", "char[]", 1, 1, 1, KF_String},
|
{NodeKind::UTF8, "UTF8", "str", 1, 1, 1, KF_String},
|
||||||
{NodeKind::UTF16, "UTF16", "wchar_t[]", 2, 1, 2, KF_String},
|
{NodeKind::UTF16, "UTF16", "wstr", 2, 1, 2, KF_String},
|
||||||
{NodeKind::Padding, "Padding", "pad", 1, 1, 1, KF_HexPreview},
|
|
||||||
{NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container},
|
{NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container},
|
||||||
{NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container},
|
{NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container},
|
||||||
};
|
};
|
||||||
@@ -128,15 +137,27 @@ inline constexpr bool isHexNode(NodeKind k) {
|
|||||||
inline constexpr bool isVectorKind(NodeKind k) {
|
inline constexpr bool isVectorKind(NodeKind k) {
|
||||||
return k == NodeKind::Vec2 || k == NodeKind::Vec3 || k == NodeKind::Vec4;
|
return k == NodeKind::Vec2 || k == NodeKind::Vec3 || k == NodeKind::Vec4;
|
||||||
}
|
}
|
||||||
|
inline constexpr bool isMatrixKind(NodeKind k) {
|
||||||
|
return k == NodeKind::Mat4x4;
|
||||||
|
}
|
||||||
|
inline constexpr bool isFuncPtr(NodeKind k) {
|
||||||
|
return k == NodeKind::FuncPtr32 || k == NodeKind::FuncPtr64;
|
||||||
|
}
|
||||||
|
// Hex types, pointer types, function pointers, and containers are not meaningful
|
||||||
|
// primitive-pointer targets — dereferencing them produces the same output as void*.
|
||||||
|
inline constexpr bool isValidPrimitivePtrTarget(NodeKind k) {
|
||||||
|
if (isHexNode(k)) return false;
|
||||||
|
if (k == NodeKind::Pointer32 || k == NodeKind::Pointer64) return false;
|
||||||
|
if (isFuncPtr(k)) return false;
|
||||||
|
if (k == NodeKind::Struct || k == NodeKind::Array) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
inline QStringList allTypeNamesForUI(bool stripBrackets = false) {
|
inline QStringList allTypeNamesForUI(bool /*stripBrackets*/ = false) {
|
||||||
QStringList out;
|
QStringList out;
|
||||||
out.reserve(std::size(kKindMeta));
|
out.reserve(std::size(kKindMeta));
|
||||||
for (const auto& m : kKindMeta) {
|
for (const auto& m : kKindMeta)
|
||||||
QString t = QString::fromLatin1(m.typeName);
|
out << QString::fromLatin1(m.typeName);
|
||||||
if (stripBrackets) t.remove(QStringLiteral("[]"));
|
|
||||||
out << t;
|
|
||||||
}
|
|
||||||
out.sort(Qt::CaseInsensitive);
|
out.sort(Qt::CaseInsensitive);
|
||||||
out.removeDuplicates();
|
out.removeDuplicates();
|
||||||
return out;
|
return out;
|
||||||
@@ -146,7 +167,6 @@ inline QStringList allTypeNamesForUI(bool stripBrackets = false) {
|
|||||||
|
|
||||||
enum Marker : int {
|
enum Marker : int {
|
||||||
M_CONT = 0,
|
M_CONT = 0,
|
||||||
M_PAD = 1,
|
|
||||||
M_PTR0 = 2,
|
M_PTR0 = 2,
|
||||||
M_CYCLE = 3,
|
M_CYCLE = 3,
|
||||||
M_ERR = 4,
|
M_ERR = 4,
|
||||||
@@ -154,6 +174,15 @@ enum Marker : int {
|
|||||||
M_HOVER = 6,
|
M_HOVER = 6,
|
||||||
M_SELECTED = 7,
|
M_SELECTED = 7,
|
||||||
M_CMD_ROW = 8,
|
M_CMD_ROW = 8,
|
||||||
|
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 ──
|
// ── Node ──
|
||||||
@@ -166,20 +195,34 @@ struct Node {
|
|||||||
QString classKeyword; // "struct", "class", or "enum" (empty = "struct")
|
QString classKeyword; // "struct", "class", or "enum" (empty = "struct")
|
||||||
uint64_t parentId = 0; // 0 = root (no parent)
|
uint64_t parentId = 0; // 0 = root (no parent)
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
bool isStatic = false; // static field — excluded from struct layout
|
||||||
|
QString offsetExpr; // C/C++ expression → absolute address (static fields only)
|
||||||
int arrayLen = 1; // Array: element count
|
int arrayLen = 1; // Array: element count
|
||||||
int strLen = 64;
|
int strLen = 64;
|
||||||
bool collapsed = false;
|
bool collapsed = false;
|
||||||
uint64_t refId = 0; // Pointer32/64: id of Struct to expand at *ptr
|
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)
|
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.
|
// Note: Returns 0 for Array-of-Struct/Array. Use tree.structSpan() for accurate size.
|
||||||
int byteSize() const {
|
int byteSize() const {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case NodeKind::UTF8: return strLen;
|
case NodeKind::UTF8: return strLen;
|
||||||
case NodeKind::UTF16: return strLen * 2;
|
case NodeKind::UTF16: return qMin(strLen, INT_MAX / 2) * 2;
|
||||||
case NodeKind::Padding: return qMax(1, arrayLen);
|
case NodeKind::Array: {
|
||||||
case NodeKind::Array: return arrayLen * sizeForKind(elementKind);
|
int elemSz = sizeForKind(elementKind);
|
||||||
|
if (elemSz <= 0) return 0;
|
||||||
|
return qMin(arrayLen, INT_MAX / elemSz) * elemSz;
|
||||||
|
}
|
||||||
|
case NodeKind::Struct:
|
||||||
|
if (classKeyword == QStringLiteral("bitfield")) {
|
||||||
|
int sz = sizeForKind(elementKind);
|
||||||
|
return sz > 0 ? sz : 4;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
default: return sizeForKind(kind);
|
default: return sizeForKind(kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,11 +238,38 @@ struct Node {
|
|||||||
o["classKeyword"] = classKeyword;
|
o["classKeyword"] = classKeyword;
|
||||||
o["parentId"] = QString::number(parentId);
|
o["parentId"] = QString::number(parentId);
|
||||||
o["offset"] = offset;
|
o["offset"] = offset;
|
||||||
|
if (isStatic)
|
||||||
|
o["isStatic"] = true;
|
||||||
|
if (!offsetExpr.isEmpty())
|
||||||
|
o["offsetExpr"] = offsetExpr;
|
||||||
o["arrayLen"] = arrayLen;
|
o["arrayLen"] = arrayLen;
|
||||||
o["strLen"] = strLen;
|
o["strLen"] = strLen;
|
||||||
o["collapsed"] = collapsed;
|
o["collapsed"] = collapsed;
|
||||||
o["refId"] = QString::number(refId);
|
o["refId"] = QString::number(refId);
|
||||||
o["elementKind"] = kindToString(elementKind);
|
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;
|
return o;
|
||||||
}
|
}
|
||||||
static Node fromJson(const QJsonObject& o) {
|
static Node fromJson(const QJsonObject& o) {
|
||||||
@@ -211,11 +281,33 @@ struct Node {
|
|||||||
n.classKeyword = o["classKeyword"].toString();
|
n.classKeyword = o["classKeyword"].toString();
|
||||||
n.parentId = o["parentId"].toString("0").toULongLong();
|
n.parentId = o["parentId"].toString("0").toULongLong();
|
||||||
n.offset = o["offset"].toInt(0);
|
n.offset = o["offset"].toInt(0);
|
||||||
n.arrayLen = o["arrayLen"].toInt(1);
|
n.isStatic = o["isStatic"].toBool(o["isHelper"].toBool(false));
|
||||||
n.strLen = o["strLen"].toInt(64);
|
n.offsetExpr = o["offsetExpr"].toString();
|
||||||
|
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 = o["collapsed"].toBool(false);
|
||||||
n.refId = o["refId"].toString("0").toULongLong();
|
n.refId = o["refId"].toString("0").toULongLong();
|
||||||
n.elementKind = kindFromString(o["elementKind"].toString("UInt8"));
|
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.append({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;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,11 +316,12 @@ struct Node {
|
|||||||
return classKeyword.isEmpty() ? QStringLiteral("struct") : classKeyword;
|
return classKeyword.isEmpty() ? QStringLiteral("struct") : classKeyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: is this a string-like array (char[] or wchar_t[])?
|
// NOTE: isStringArray() was checking UInt8/UInt16 instead of UTF8/UTF16.
|
||||||
bool isStringArray() const {
|
// Currently unused — commented out until a caller needs it.
|
||||||
return kind == NodeKind::Array &&
|
// bool isStringArray() const {
|
||||||
(elementKind == NodeKind::UInt8 || elementKind == NodeKind::UInt16);
|
// return kind == NodeKind::Array &&
|
||||||
}
|
// (elementKind == NodeKind::UTF8 || elementKind == NodeKind::UTF16);
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── NodeTree ──
|
// ── NodeTree ──
|
||||||
@@ -236,6 +329,8 @@ struct Node {
|
|||||||
struct NodeTree {
|
struct NodeTree {
|
||||||
QVector<Node> nodes;
|
QVector<Node> nodes;
|
||||||
uint64_t baseAddress = 0x00400000;
|
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;
|
uint64_t m_nextId = 1;
|
||||||
mutable QHash<uint64_t, int> m_idCache;
|
mutable QHash<uint64_t, int> m_idCache;
|
||||||
|
|
||||||
@@ -349,18 +444,20 @@ struct NodeTree {
|
|||||||
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
|
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
|
||||||
for (int ci : kids) {
|
for (int ci : kids) {
|
||||||
const Node& c = nodes[ci];
|
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)
|
int sz = (c.kind == NodeKind::Struct || c.kind == NodeKind::Array)
|
||||||
? structSpan(c.id, childMap, visited) : c.byteSize();
|
? structSpan(c.id, childMap, visited) : c.byteSize();
|
||||||
int end = c.offset + sz;
|
int end = c.offset + sz;
|
||||||
if (end > maxEnd) maxEnd = end;
|
if (end > maxEnd) maxEnd = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Embedded struct reference: no own children but refId points to a struct definition
|
||||||
|
if (kids.isEmpty() && node.kind == NodeKind::Struct && node.refId != 0)
|
||||||
|
maxEnd = qMax(maxEnd, structSpan(node.refId, childMap, visited));
|
||||||
|
|
||||||
return qMax(declaredSize, maxEnd);
|
return qMax(declaredSize, maxEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute natural alignment of a struct (max alignment of direct children)
|
|
||||||
int computeStructAlignment(uint64_t structId) const;
|
|
||||||
|
|
||||||
// Batch selection normalizers
|
// Batch selection normalizers
|
||||||
QSet<uint64_t> normalizePreferAncestors(const QSet<uint64_t>& ids) const;
|
QSet<uint64_t> normalizePreferAncestors(const QSet<uint64_t>& ids) const;
|
||||||
QSet<uint64_t> normalizePreferDescendants(const QSet<uint64_t>& ids) const;
|
QSet<uint64_t> normalizePreferDescendants(const QSet<uint64_t>& ids) const;
|
||||||
@@ -368,6 +465,10 @@ struct NodeTree {
|
|||||||
QJsonObject toJson() const {
|
QJsonObject toJson() const {
|
||||||
QJsonObject o;
|
QJsonObject o;
|
||||||
o["baseAddress"] = QString::number(baseAddress, 16);
|
o["baseAddress"] = QString::number(baseAddress, 16);
|
||||||
|
if (!baseAddressFormula.isEmpty())
|
||||||
|
o["baseAddressFormula"] = baseAddressFormula;
|
||||||
|
if (pointerSize != 8)
|
||||||
|
o["pointerSize"] = pointerSize;
|
||||||
o["nextId"] = QString::number(m_nextId);
|
o["nextId"] = QString::number(m_nextId);
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
for (const auto& n : nodes) arr.append(n.toJson());
|
for (const auto& n : nodes) arr.append(n.toJson());
|
||||||
@@ -378,6 +479,8 @@ struct NodeTree {
|
|||||||
static NodeTree fromJson(const QJsonObject& o) {
|
static NodeTree fromJson(const QJsonObject& o) {
|
||||||
NodeTree t;
|
NodeTree t;
|
||||||
t.baseAddress = o["baseAddress"].toString("400000").toULongLong(nullptr, 16);
|
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();
|
t.m_nextId = o["nextId"].toString("1").toULongLong();
|
||||||
QJsonArray arr = o["nodes"].toArray();
|
QJsonArray arr = o["nodes"].toArray();
|
||||||
for (const auto& v : arr) {
|
for (const auto& v : arr) {
|
||||||
@@ -390,6 +493,66 @@ struct NodeTree {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Value History (ring buffer for heatmap) ──
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
void record(const QString& v) {
|
||||||
|
if (count > 0) {
|
||||||
|
int last = (head + kCapacity - 1) % kCapacity;
|
||||||
|
if (values[last] == v) return; // no change
|
||||||
|
}
|
||||||
|
values[head] = v;
|
||||||
|
timestamps[head] = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
head = (head + 1) % kCapacity;
|
||||||
|
if (count < INT_MAX) count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
count = 0;
|
||||||
|
head = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int uniqueCount() const { return qMin(count, kCapacity); }
|
||||||
|
|
||||||
|
// 0=static, 1=cold(2 unique), 2=warm(3-4), 3=hot(5+)
|
||||||
|
int heatLevel() const {
|
||||||
|
if (count <= 1) return 0;
|
||||||
|
if (count == 2) return 1;
|
||||||
|
if (count <= 4) return 2;
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString last() const {
|
||||||
|
if (count == 0) return {};
|
||||||
|
return values[(head + kCapacity - 1) % kCapacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate from oldest to newest (up to uniqueCount entries)
|
||||||
|
template<typename Fn>
|
||||||
|
void forEach(Fn&& fn) const {
|
||||||
|
int n = uniqueCount();
|
||||||
|
int start = (head + kCapacity - n) % kCapacity;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
fn(values[(start + i) % kCapacity]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate with timestamps from newest to oldest
|
||||||
|
template<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 ──
|
// ── LineMeta ──
|
||||||
|
|
||||||
enum class LineKind : uint8_t {
|
enum class LineKind : uint8_t {
|
||||||
@@ -402,6 +565,29 @@ static constexpr uint64_t kCommandRowId = UINT64_MAX;
|
|||||||
static constexpr int kCommandRowLine = 0;
|
static constexpr int kCommandRowLine = 0;
|
||||||
static constexpr int kFirstDataLine = 1;
|
static constexpr int kFirstDataLine = 1;
|
||||||
static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL;
|
static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL;
|
||||||
|
static constexpr uint64_t kArrayElemBit = 0x4000000000000000ULL; // marks array element selection
|
||||||
|
static constexpr uint64_t kArrayElemShift = 48; // bits 48-61 hold element index
|
||||||
|
static constexpr uint64_t kArrayElemMask = 0x3FFF000000000000ULL; // 14 bits → max 16383 elements
|
||||||
|
|
||||||
|
// Encode an array element selection ID: nodeId | kArrayElemBit | (elemIdx << 48)
|
||||||
|
inline uint64_t makeArrayElemSelId(uint64_t nodeId, int elemIdx) {
|
||||||
|
return nodeId | kArrayElemBit | ((uint64_t)(elemIdx & 0x3FFF) << 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 = 48;
|
||||||
|
static constexpr uint64_t kMemberSubMask = 0x3FFF000000000000ULL;
|
||||||
|
|
||||||
|
inline uint64_t makeMemberSelId(uint64_t nodeId, int subLine) {
|
||||||
|
return nodeId | kMemberBit | ((uint64_t)(subLine & 0x3FFF) << kMemberSubShift);
|
||||||
|
}
|
||||||
|
inline int memberSubFromSelId(uint64_t selId) {
|
||||||
|
return (int)((selId & kMemberSubMask) >> kMemberSubShift);
|
||||||
|
}
|
||||||
|
|
||||||
struct LineMeta {
|
struct LineMeta {
|
||||||
int nodeIdx = -1;
|
int nodeIdx = -1;
|
||||||
@@ -421,13 +607,19 @@ struct LineMeta {
|
|||||||
int arrayCount = 0; // Array: total element count
|
int arrayCount = 0; // Array: total element count
|
||||||
int arrayElementIdx = -1; // Index of this element within parent array (-1 if not array element)
|
int arrayElementIdx = -1; // Index of this element within parent array (-1 if not array element)
|
||||||
QString offsetText;
|
QString offsetText;
|
||||||
|
uint64_t offsetAddr = 0; // Raw absolute address (for margin toggle)
|
||||||
|
uint64_t ptrBase = 0; // Pointer expansion base (non-zero = use for RVA)
|
||||||
uint32_t markerMask = 0;
|
uint32_t markerMask = 0;
|
||||||
bool dataChanged = false; // true if any byte in this node changed since last refresh
|
bool dataChanged = false; // true if any byte in this node changed since last refresh
|
||||||
|
int heatLevel = 0; // 0=static, 1=cold, 2=warm, 3=hot (from ValueHistory)
|
||||||
QVector<int> changedByteIndices; // Hex preview: which byte indices (0-based) changed on this line
|
QVector<int> changedByteIndices; // Hex preview: which byte indices (0-based) changed on this line
|
||||||
int lineByteCount = 0; // Hex preview: actual data byte count on this line
|
int lineByteCount = 0; // Hex preview: actual data byte count on this line
|
||||||
int effectiveTypeW = 14; // Per-line type column width used for rendering
|
int effectiveTypeW = 14; // Per-line type column width used for rendering
|
||||||
int effectiveNameW = 22; // Per-line name column width used for rendering
|
int effectiveNameW = 22; // Per-line name column width used for rendering
|
||||||
QString pointerTargetName; // Resolved target type name for Pointer32/64 (empty = "void")
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool isSyntheticLine(const LineMeta& lm) {
|
inline bool isSyntheticLine(const LineMeta& lm) {
|
||||||
@@ -439,6 +631,9 @@ inline bool isSyntheticLine(const LineMeta& lm) {
|
|||||||
struct LayoutInfo {
|
struct LayoutInfo {
|
||||||
int typeW = 14; // Effective type column width (default = kColType)
|
int typeW = 14; // Effective type column width (default = kColType)
|
||||||
int nameW = 22; // Effective name column width (default = kColName)
|
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 ──
|
// ── ComposeResult ──
|
||||||
@@ -460,7 +655,7 @@ namespace cmd {
|
|||||||
struct Insert { Node node; QVector<OffsetAdj> offAdjs; };
|
struct Insert { Node node; QVector<OffsetAdj> offAdjs; };
|
||||||
struct Remove { uint64_t nodeId; QVector<Node> subtree;
|
struct Remove { uint64_t nodeId; QVector<Node> subtree;
|
||||||
QVector<OffsetAdj> offAdjs; };
|
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 WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; };
|
||||||
struct ChangeArrayMeta { uint64_t nodeId;
|
struct ChangeArrayMeta { uint64_t nodeId;
|
||||||
NodeKind oldElementKind, newElementKind;
|
NodeKind oldElementKind, newElementKind;
|
||||||
@@ -470,13 +665,18 @@ namespace cmd {
|
|||||||
struct ChangeStructTypeName { uint64_t nodeId; QString oldName, newName; };
|
struct ChangeStructTypeName { uint64_t nodeId; QString oldName, newName; };
|
||||||
struct ChangeClassKeyword { uint64_t nodeId; QString oldKeyword, newKeyword; };
|
struct ChangeClassKeyword { uint64_t nodeId; QString oldKeyword, newKeyword; };
|
||||||
struct ChangeOffset { uint64_t nodeId; int oldOffset, newOffset; };
|
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; };
|
||||||
}
|
}
|
||||||
|
|
||||||
using Command = std::variant<
|
using Command = std::variant<
|
||||||
cmd::ChangeKind, cmd::Rename, cmd::Collapse,
|
cmd::ChangeKind, cmd::Rename, cmd::Collapse,
|
||||||
cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes,
|
cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes,
|
||||||
cmd::ChangeArrayMeta, cmd::ChangePointerRef, cmd::ChangeStructTypeName,
|
cmd::ChangeArrayMeta, cmd::ChangePointerRef, cmd::ChangeStructTypeName,
|
||||||
cmd::ChangeClassKeyword, cmd::ChangeOffset
|
cmd::ChangeClassKeyword, cmd::ChangeOffset, cmd::ChangeEnumMembers,
|
||||||
|
cmd::ChangeOffsetExpr, cmd::ToggleStatic
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// ── Column spans (for inline editing) ──
|
// ── Column spans (for inline editing) ──
|
||||||
@@ -489,13 +689,13 @@ struct ColumnSpan {
|
|||||||
|
|
||||||
enum class EditTarget { Name, Type, Value, BaseAddress, Source, ArrayIndex, ArrayCount,
|
enum class EditTarget { Name, Type, Value, BaseAddress, Source, ArrayIndex, ArrayCount,
|
||||||
ArrayElementType, ArrayElementCount, PointerTarget,
|
ArrayElementType, ArrayElementCount, PointerTarget,
|
||||||
RootClassType, RootClassName };
|
RootClassType, RootClassName, TypeSelector, StaticExpr };
|
||||||
|
|
||||||
// Column layout constants (shared with format.cpp span computation)
|
// Column layout constants (shared with format.cpp span computation)
|
||||||
inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line
|
inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line
|
||||||
inline constexpr int kColType = 14; // Max type column width (fits "uint64_t[999]")
|
inline constexpr int kColType = 14; // Max type column width (fits "uint64_t[999]")
|
||||||
inline constexpr int kColName = 22;
|
inline constexpr int kColName = 22;
|
||||||
inline constexpr int kColValue = 32;
|
inline constexpr int kColValue = 96;
|
||||||
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
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 kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
|
||||||
inline constexpr int kSepWidth = 1;
|
inline constexpr int kSepWidth = 1;
|
||||||
@@ -503,22 +703,23 @@ inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uin
|
|||||||
inline constexpr int kMaxTypeW = 128; // Maximum type column width
|
inline constexpr int kMaxTypeW = 128; // Maximum type column width
|
||||||
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
||||||
inline constexpr int kMaxNameW = 128; // Maximum name column width
|
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) {
|
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;
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
return {ind, ind + typeW, true};
|
return {ind, ind + typeW, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int nameW = kColName) {
|
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 ind = kFoldCol + lm.depth * 3;
|
||||||
int start = ind + typeW + kSepWidth;
|
int start = ind + typeW + kSepWidth;
|
||||||
|
|
||||||
// Hex/Padding: ASCII preview takes the name column position (8 chars)
|
// Hex: ASCII preview occupies the name column (padded to nameW)
|
||||||
if (isHexPreview(lm.nodeKind))
|
if (isHexPreview(lm.nodeKind))
|
||||||
return {start, start + 8, true};
|
return {start, start + nameW, true};
|
||||||
|
|
||||||
return {start, start + nameW, true};
|
return {start, start + nameW, true};
|
||||||
}
|
}
|
||||||
@@ -526,44 +727,77 @@ 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) {
|
inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW = kColType, int nameW = kColName) {
|
||||||
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer ||
|
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer ||
|
||||||
lm.lineKind == LineKind::ArrayElementSeparator) return {};
|
lm.lineKind == LineKind::ArrayElementSeparator) return {};
|
||||||
|
if (lm.isMemberLine) return {};
|
||||||
int ind = kFoldCol + lm.depth * 3;
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
|
|
||||||
// Hex/Padding layout: [Type][sep][ASCII(8)][sep][hex bytes(23)]
|
// Hex uses nameW for ASCII column (same as regular name column)
|
||||||
bool isHexPad = isHexPreview(lm.nodeKind);
|
bool isHex = isHexPreview(lm.nodeKind);
|
||||||
int valWidth = isHexPad ? 23 : kColValue;
|
int valWidth = isHex ? 23 : kColValue;
|
||||||
|
|
||||||
|
int prefixW = typeW + nameW + 2 * kSepWidth;
|
||||||
|
|
||||||
if (lm.isContinuation) {
|
if (lm.isContinuation) {
|
||||||
int prefixW = isHexPad
|
|
||||||
? (typeW + kSepWidth + 8 + kSepWidth)
|
|
||||||
: (typeW + nameW + 2 * kSepWidth);
|
|
||||||
int start = ind + prefixW;
|
int start = ind + prefixW;
|
||||||
return {start, start + valWidth, true};
|
return {start, start + valWidth, true};
|
||||||
}
|
}
|
||||||
if (lm.lineKind != LineKind::Field) return {};
|
if (lm.lineKind != LineKind::Field) return {};
|
||||||
|
|
||||||
int start = isHexPad
|
int start = ind + prefixW;
|
||||||
? (ind + typeW + kSepWidth + 8 + kSepWidth)
|
|
||||||
: (ind + typeW + kSepWidth + nameW + kSepWidth);
|
|
||||||
return {start, start + valWidth, true};
|
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) {
|
inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW = kColType, int nameW = kColName) {
|
||||||
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
|
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
|
||||||
int ind = kFoldCol + lm.depth * 3;
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
|
|
||||||
bool isHexPad = isHexPreview(lm.nodeKind);
|
bool isHex = isHexPreview(lm.nodeKind);
|
||||||
int valWidth = isHexPad ? 23 : kColValue;
|
int valWidth = isHex ? 23 : kColValue;
|
||||||
|
|
||||||
|
int prefixW = typeW + nameW + 2 * kSepWidth;
|
||||||
int start;
|
int start;
|
||||||
if (lm.isContinuation) {
|
if (lm.isContinuation) {
|
||||||
int prefixW = isHexPad
|
|
||||||
? (typeW + kSepWidth + 8 + kSepWidth)
|
|
||||||
: (typeW + nameW + 2 * kSepWidth);
|
|
||||||
start = ind + prefixW + valWidth;
|
start = ind + prefixW + valWidth;
|
||||||
} else {
|
} else {
|
||||||
start = isHexPad
|
start = ind + prefixW + valWidth;
|
||||||
? (ind + typeW + kSepWidth + 8 + kSepWidth + valWidth)
|
|
||||||
: (ind + typeW + kSepWidth + nameW + kSepWidth + valWidth);
|
|
||||||
}
|
}
|
||||||
return {start, lineLength, start < lineLength};
|
return {start, lineLength, start < lineLength};
|
||||||
}
|
}
|
||||||
@@ -572,50 +806,56 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
|||||||
// Line format: "source▾ · 0x140000000"
|
// Line format: "source▾ · 0x140000000"
|
||||||
|
|
||||||
inline ColumnSpan commandRowSrcSpan(const QString& lineText) {
|
inline ColumnSpan commandRowSrcSpan(const QString& lineText) {
|
||||||
int idx = lineText.indexOf(QStringLiteral(" \u00B7"));
|
// Source label ends at the ▾ dropdown arrow
|
||||||
if (idx < 0) return {};
|
int arrow = lineText.indexOf(QChar(0x25BE));
|
||||||
|
if (arrow < 0) return {};
|
||||||
int start = 0;
|
int start = 0;
|
||||||
while (start < idx && !lineText[start].isLetterOrNumber()
|
while (start < arrow && !lineText[start].isLetterOrNumber()
|
||||||
&& lineText[start] != '<' && lineText[start] != '\'') start++;
|
&& lineText[start] != '<' && lineText[start] != '\'') start++;
|
||||||
if (start >= idx) return {};
|
if (start >= arrow) return {};
|
||||||
// Exclude trailing ▾ from the editable span
|
return {start, arrow, true};
|
||||||
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};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CommandRow root-class spans ──
|
// ── CommandRow root-class spans ──
|
||||||
// Combined CommandRow format ends with: " struct▾ ClassName {"
|
// Combined CommandRow format ends with: " struct ClassName {"
|
||||||
|
|
||||||
inline int commandRowRootStart(const QString& lineText) {
|
inline int commandRowRootStart(const QString& lineText) {
|
||||||
int best = -1;
|
int best = -1;
|
||||||
int i;
|
int i;
|
||||||
i = lineText.lastIndexOf(QStringLiteral("struct\u25BE"));
|
// Match "struct " / "class " / "enum " as whole words before the class name
|
||||||
|
i = lineText.lastIndexOf(QStringLiteral("struct "));
|
||||||
if (i > best) best = i;
|
if (i > best) best = i;
|
||||||
i = lineText.lastIndexOf(QStringLiteral("class\u25BE"));
|
i = lineText.lastIndexOf(QStringLiteral("class "));
|
||||||
if (i > best) best = i;
|
if (i > best) best = i;
|
||||||
i = lineText.lastIndexOf(QStringLiteral("enum\u25BE"));
|
i = lineText.lastIndexOf(QStringLiteral("enum "));
|
||||||
if (i > best) best = i;
|
if (i > best) best = i;
|
||||||
return best;
|
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) {
|
inline ColumnSpan commandRowRootTypeSpan(const QString& lineText) {
|
||||||
int start = commandRowRootStart(lineText);
|
int start = commandRowRootStart(lineText);
|
||||||
if (start < 0) return {};
|
if (start < 0) return {};
|
||||||
int end = start;
|
int end = start;
|
||||||
while (end < lineText.size() && lineText[end] != QChar(' ')
|
while (end < lineText.size() && lineText[end] != QChar(' ')) end++;
|
||||||
&& lineText[end] != QChar(0x25BE)) end++;
|
|
||||||
if (end <= start) return {};
|
if (end <= start) return {};
|
||||||
return {start, end, true};
|
return {start, end, true};
|
||||||
}
|
}
|
||||||
@@ -635,6 +875,16 @@ inline ColumnSpan commandRowRootNameSpan(const QString& lineText) {
|
|||||||
return {nameStart, nameEnd, true};
|
return {nameStart, nameEnd, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── CommandRow type-selector chevron span ──
|
||||||
|
// Detects "[▸]" at the start of the command row text
|
||||||
|
|
||||||
|
inline ColumnSpan commandRowChevronSpan(const QString& lineText) {
|
||||||
|
if (lineText.size() < 3) return {};
|
||||||
|
if (lineText[0] == '[' && lineText[1] == QChar(0x25B8) && lineText[2] == ']')
|
||||||
|
return {0, qMin(4, (int)lineText.size()), true}; // include trailing space for easier clicking
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// ── Array element type/count spans (within type column of array headers) ──
|
// ── Array element type/count spans (within type column of array headers) ──
|
||||||
// Line format: " int32_t[10] name {"
|
// Line format: " int32_t[10] name {"
|
||||||
// arrayElemTypeSpan covers "int32_t", arrayElemCountSpan covers "10"
|
// arrayElemTypeSpan covers "int32_t", arrayElemCountSpan covers "10"
|
||||||
@@ -657,6 +907,16 @@ inline ColumnSpan arrayElemCountSpanFor(const LineMeta& lm, const QString& lineT
|
|||||||
return {openBracket + 1, closeBracket, true};
|
return {openBracket + 1, closeBracket, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Click-area version: includes brackets [N] for hit testing
|
||||||
|
inline ColumnSpan arrayElemCountClickSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||||
|
if (lm.lineKind != LineKind::Header || !lm.isArrayHeader) return {};
|
||||||
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
|
int openBracket = lineText.indexOf('[', ind);
|
||||||
|
int closeBracket = lineText.indexOf(']', openBracket);
|
||||||
|
if (openBracket < 0 || closeBracket < 0 || closeBracket <= openBracket + 1) return {};
|
||||||
|
return {openBracket, closeBracket + 1, true};
|
||||||
|
}
|
||||||
|
|
||||||
// ── Pointer kind/target spans (within type column of pointer fields) ──
|
// ── Pointer kind/target spans (within type column of pointer fields) ──
|
||||||
// Line format: " void* name -> 0x..."
|
// Line format: " void* name -> 0x..."
|
||||||
// pointerTargetSpan covers the target name before '*'
|
// pointerTargetSpan covers the target name before '*'
|
||||||
@@ -739,17 +999,18 @@ namespace fmt {
|
|||||||
QString fmtNodeLine(const Node& node, const Provider& prov,
|
QString fmtNodeLine(const Node& node, const Provider& prov,
|
||||||
uint64_t addr, int depth, int subLine = 0,
|
uint64_t addr, int depth, int subLine = 0,
|
||||||
const QString& comment = {}, int colType = kColType, int colName = kColName,
|
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);
|
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 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);
|
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 structTypeName(const Node& node); // Full type string for struct headers
|
||||||
QString arrayTypeName(NodeKind elemKind, int count);
|
QString arrayTypeName(NodeKind elemKind, int count, const QString& structName = {});
|
||||||
QString pointerTypeName(NodeKind kind, const QString& targetName);
|
QString pointerTypeName(NodeKind kind, const QString& targetName);
|
||||||
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
||||||
const Provider& prov, uint64_t addr,
|
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 validateBaseAddress(const QString& text);
|
||||||
QString indent(int depth);
|
QString indent(int depth);
|
||||||
QString readValue(const Node& node, const Provider& prov,
|
QString readValue(const Node& node, const Provider& prov,
|
||||||
@@ -759,10 +1020,17 @@ namespace fmt {
|
|||||||
QByteArray parseValue(NodeKind kind, const QString& text, bool* ok);
|
QByteArray parseValue(NodeKind kind, const QString& text, bool* ok);
|
||||||
QByteArray parseAsciiValue(const QString& text, int expectedSize, bool* ok);
|
QByteArray parseAsciiValue(const QString& text, int expectedSize, bool* ok);
|
||||||
QString validateValue(NodeKind kind, const QString& text);
|
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
|
} // namespace fmt
|
||||||
|
|
||||||
// ── Compose function forward declaration ──
|
// ── Compose function forward declaration ──
|
||||||
|
|
||||||
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0);
|
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0,
|
||||||
|
bool compactColumns = false, bool treeLines = false);
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
76
src/disasm.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#include "disasm.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <fadec.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
QString disassemble(const QByteArray& bytes, uint64_t baseAddr, int bitness, int maxBytes) {
|
||||||
|
if (bytes.isEmpty() || (bitness != 32 && bitness != 64))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int len = qMin((int)bytes.size(), maxBytes);
|
||||||
|
const auto* buf = reinterpret_cast<const uint8_t*>(bytes.constData());
|
||||||
|
|
||||||
|
QString result;
|
||||||
|
int off = 0;
|
||||||
|
while (off < len) {
|
||||||
|
FdInstr instr;
|
||||||
|
int ret = fd_decode(buf + off, len - off, bitness, baseAddr + off, &instr);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
char fmtBuf[128];
|
||||||
|
fd_format(&instr, fmtBuf, sizeof(fmtBuf));
|
||||||
|
|
||||||
|
if (!result.isEmpty())
|
||||||
|
result += QLatin1Char('\n');
|
||||||
|
result += QStringLiteral("%1 %2")
|
||||||
|
.arg(baseAddr + off, bitness == 64 ? 16 : 8, 16, QLatin1Char('0'))
|
||||||
|
.arg(QString::fromLatin1(fmtBuf));
|
||||||
|
|
||||||
|
off += ret;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString hexDump(const QByteArray& bytes, uint64_t baseAddr, int maxBytes) {
|
||||||
|
if (bytes.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int len = qMin((int)bytes.size(), maxBytes);
|
||||||
|
QString result;
|
||||||
|
|
||||||
|
for (int off = 0; off < len; off += 16) {
|
||||||
|
int lineLen = qMin(16, len - off);
|
||||||
|
|
||||||
|
if (!result.isEmpty())
|
||||||
|
result += QLatin1Char('\n');
|
||||||
|
|
||||||
|
// Address
|
||||||
|
bool wide = (baseAddr + len > 0xFFFFFFFFULL);
|
||||||
|
result += QStringLiteral("%1 ").arg(baseAddr + off, wide ? 16 : 8, 16, QLatin1Char('0'));
|
||||||
|
|
||||||
|
// Hex bytes
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (i < lineLen) {
|
||||||
|
uint8_t b = static_cast<uint8_t>(bytes[off + i]);
|
||||||
|
result += QStringLiteral("%1 ").arg(b, 2, 16, QLatin1Char('0'));
|
||||||
|
} else {
|
||||||
|
result += QStringLiteral(" ");
|
||||||
|
}
|
||||||
|
if (i == 7) result += QLatin1Char(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASCII
|
||||||
|
result += QLatin1Char(' ');
|
||||||
|
for (int i = 0; i < lineLen; i++) {
|
||||||
|
char c = bytes[off + i];
|
||||||
|
result += (c >= 0x20 && c < 0x7f) ? QLatin1Char(c) : QLatin1Char('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
15
src/disasm.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
// Disassemble up to maxBytes of x86 code, returning formatted asm lines.
|
||||||
|
// bitness: 32 or 64. Returns one line per instruction, prefixed with offset.
|
||||||
|
QString disassemble(const QByteArray& bytes, uint64_t baseAddr, int bitness, int maxBytes = 128);
|
||||||
|
|
||||||
|
// Format bytes as hex dump lines (16 bytes per line with ASCII sidebar).
|
||||||
|
QString hexDump(const QByteArray& bytes, uint64_t baseAddr, int maxBytes = 128);
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
2024
src/editor.cpp
53
src/editor.h
@@ -1,9 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
#include "themes/theme.h"
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
class QLineEdit;
|
||||||
class QsciScintilla;
|
class QsciScintilla;
|
||||||
class QsciLexerCPP;
|
class QsciLexerCPP;
|
||||||
|
|
||||||
@@ -26,9 +29,12 @@ public:
|
|||||||
void restoreViewState(const ViewState& vs);
|
void restoreViewState(const ViewState& vs);
|
||||||
|
|
||||||
QsciScintilla* scintilla() const { return m_sci; }
|
QsciScintilla* scintilla() const { return m_sci; }
|
||||||
|
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
|
||||||
const LineMeta* metaForLine(int line) const;
|
const LineMeta* metaForLine(int line) const;
|
||||||
int currentNodeIndex() const;
|
int currentNodeIndex() const;
|
||||||
void scrollToNodeId(uint64_t nodeId);
|
void scrollToNodeId(uint64_t nodeId);
|
||||||
|
void showFindBar();
|
||||||
|
void dismissHistoryPopup();
|
||||||
|
|
||||||
// ── Column span computation ──
|
// ── Column span computation ──
|
||||||
static ColumnSpan typeSpan(const LineMeta& lm, int typeW = kColType);
|
static ColumnSpan typeSpan(const LineMeta& lm, int typeW = kColType);
|
||||||
@@ -40,16 +46,26 @@ public:
|
|||||||
|
|
||||||
// ── Inline editing ──
|
// ── Inline editing ──
|
||||||
bool isEditing() const { return m_editState.active; }
|
bool isEditing() const { return m_editState.active; }
|
||||||
bool beginInlineEdit(EditTarget target, int line = -1);
|
bool beginInlineEdit(EditTarget target, int line = -1, int col = -1);
|
||||||
void cancelInlineEdit();
|
void cancelInlineEdit();
|
||||||
|
void setStaticCompletions(const QStringList& words) { m_staticCompletions = words; }
|
||||||
|
|
||||||
void applySelectionOverlay(const QSet<uint64_t>& selIds);
|
void applySelectionOverlay(const QSet<uint64_t>& selIds);
|
||||||
void setCommandRowText(const QString& line);
|
void setCommandRowText(const QString& line);
|
||||||
void setEditorFont(const QString& fontName);
|
void setEditorFont(const QString& fontName);
|
||||||
static void setGlobalFontName(const QString& fontName);
|
static void setGlobalFontName(const QString& fontName);
|
||||||
|
static QString globalFontName();
|
||||||
|
void applyTheme(const Theme& theme);
|
||||||
|
|
||||||
// Custom type names (struct types from the tree) shown in type picker + lexer GlobalClass coloring
|
// Custom type names (struct types from the tree) shown in type picker + lexer GlobalClass coloring
|
||||||
|
QString textWithMargins() const;
|
||||||
void setCustomTypeNames(const QStringList& names);
|
void setCustomTypeNames(const QStringList& names);
|
||||||
|
void setValueHistoryRef(const QHash<uint64_t, ValueHistory>* ref) { m_valueHistory = ref; }
|
||||||
|
void setProviderRef(const Provider* prov, const Provider* realProv, const NodeTree* tree) {
|
||||||
|
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
|
// Saved sources for quick-switch in source picker
|
||||||
void setSavedSources(const QVector<SavedSourceDisplay>& sources) { m_savedSourceDisplay = sources; }
|
void setSavedSources(const QVector<SavedSourceDisplay>& sources) { m_savedSourceDisplay = sources; }
|
||||||
@@ -57,10 +73,15 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void marginClicked(int margin, int line, Qt::KeyboardModifiers mods);
|
void marginClicked(int margin, int line, Qt::KeyboardModifiers mods);
|
||||||
void contextMenuRequested(int line, int nodeIdx, int subLine, QPoint globalPos);
|
void contextMenuRequested(int line, int nodeIdx, int subLine, QPoint globalPos);
|
||||||
|
void keywordConvertRequested(const QString& newKeyword);
|
||||||
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
|
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
|
||||||
void inlineEditCommitted(int nodeIdx, int subLine,
|
void inlineEditCommitted(int nodeIdx, int subLine,
|
||||||
EditTarget target, const QString& text);
|
EditTarget target, const QString& text,
|
||||||
|
uint64_t resolvedAddr = 0);
|
||||||
void inlineEditCancelled();
|
void inlineEditCancelled();
|
||||||
|
void typeSelectorRequested();
|
||||||
|
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
|
||||||
|
void insertAboveRequested(int nodeIdx, NodeKind kind);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
@@ -71,6 +92,9 @@ private:
|
|||||||
QVector<LineMeta> m_meta;
|
QVector<LineMeta> m_meta;
|
||||||
LayoutInfo m_layout; // cached from ComposeResult
|
LayoutInfo m_layout; // cached from ComposeResult
|
||||||
|
|
||||||
|
// ── Toggle: absolute vs relative offset margin
|
||||||
|
bool m_relativeOffsets = true;
|
||||||
|
|
||||||
int m_marginStyleBase = -1;
|
int m_marginStyleBase = -1;
|
||||||
int m_hintLine = -1;
|
int m_hintLine = -1;
|
||||||
|
|
||||||
@@ -79,8 +103,12 @@ private:
|
|||||||
bool m_hoverInside = false;
|
bool m_hoverInside = false;
|
||||||
uint64_t m_hoveredNodeId = 0;
|
uint64_t m_hoveredNodeId = 0;
|
||||||
int m_hoveredLine = -1;
|
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;
|
QSet<uint64_t> m_currentSelIds;
|
||||||
QVector<int> m_hoverSpanLines; // Lines with hover span indicators
|
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 ──
|
// ── Drag selection ──
|
||||||
bool m_dragging = false;
|
bool m_dragging = false;
|
||||||
bool m_dragStarted = false; // true once drag threshold exceeded
|
bool m_dragStarted = false; // true once drag threshold exceeded
|
||||||
@@ -110,6 +138,7 @@ private:
|
|||||||
bool lastValidationOk = true; // track state to avoid redundant updates
|
bool lastValidationOk = true; // track state to avoid redundant updates
|
||||||
};
|
};
|
||||||
InlineEditState m_editState;
|
InlineEditState m_editState;
|
||||||
|
QStringList m_staticCompletions; // autocomplete words for StaticExpr editing
|
||||||
|
|
||||||
// ── Tab cycling state ──
|
// ── Tab cycling state ──
|
||||||
EditTarget m_lastTabTarget = EditTarget::Value;
|
EditTarget m_lastTabTarget = EditTarget::Value;
|
||||||
@@ -120,7 +149,23 @@ private:
|
|||||||
// ── Saved sources for quick-switch ──
|
// ── Saved sources for quick-switch ──
|
||||||
QVector<SavedSourceDisplay> m_savedSourceDisplay;
|
QVector<SavedSourceDisplay> m_savedSourceDisplay;
|
||||||
|
|
||||||
|
// ── 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_structPreviewPopup = nullptr; // StructPreviewPopup (file-local class in editor.cpp)
|
||||||
|
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();
|
||||||
|
|
||||||
// ── Reentrancy guards ──
|
// ── Reentrancy guards ──
|
||||||
|
bool m_applyingDocument = false;
|
||||||
bool m_clampingSelection = false;
|
bool m_clampingSelection = false;
|
||||||
bool m_updatingComment = false;
|
bool m_updatingComment = false;
|
||||||
|
|
||||||
@@ -132,10 +177,12 @@ private:
|
|||||||
void allocateMarginStyles();
|
void allocateMarginStyles();
|
||||||
|
|
||||||
void applyMarginText(const QVector<LineMeta>& meta);
|
void applyMarginText(const QVector<LineMeta>& meta);
|
||||||
|
void reformatMargins();
|
||||||
void applyMarkers(const QVector<LineMeta>& meta);
|
void applyMarkers(const QVector<LineMeta>& meta);
|
||||||
void applyFoldLevels(const QVector<LineMeta>& meta);
|
void applyFoldLevels(const QVector<LineMeta>& meta);
|
||||||
void applyHexDimming(const QVector<LineMeta>& meta);
|
void applyHexDimming(const QVector<LineMeta>& meta);
|
||||||
void applyDataChangedHighlight(const QVector<LineMeta>& meta);
|
void applyHeatmapHighlight(const QVector<LineMeta>& meta);
|
||||||
|
void applySymbolColoring(const QVector<LineMeta>& meta);
|
||||||
void applyBaseAddressColoring(const QVector<LineMeta>& meta);
|
void applyBaseAddressColoring(const QVector<LineMeta>& meta);
|
||||||
void applyCommandRowPills();
|
void applyCommandRowPills();
|
||||||
|
|
||||||
|
|||||||
143
src/examples/Demo.rcx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
{
|
||||||
|
"baseAddress": "0",
|
||||||
|
"nextId": "20",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"kind": "Struct",
|
||||||
|
"name": "player",
|
||||||
|
"structTypeName": "PlayerEntity",
|
||||||
|
"classKeyword": "class",
|
||||||
|
"parentId": "0",
|
||||||
|
"offset": 0,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"kind": "Pointer64",
|
||||||
|
"name": "__vptr",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 0,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"kind": "Int32",
|
||||||
|
"name": "health",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 8,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"kind": "Int32",
|
||||||
|
"name": "armor",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 12,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"kind": "Float",
|
||||||
|
"name": "pos_x",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 16,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"kind": "Float",
|
||||||
|
"name": "pos_y",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 20,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"kind": "Float",
|
||||||
|
"name": "pos_z",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 24,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8",
|
||||||
|
"kind": "Hex32",
|
||||||
|
"name": "pad_1C",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 28,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9",
|
||||||
|
"kind": "Pointer64",
|
||||||
|
"name": "name",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 32,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64,
|
||||||
|
"ptrDepth": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10",
|
||||||
|
"kind": "UInt64",
|
||||||
|
"name": "flags",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 40,
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"kind": "Hex64",
|
||||||
|
"name": "static_field",
|
||||||
|
"parentId": "1",
|
||||||
|
"offset": 0,
|
||||||
|
"isStatic": true,
|
||||||
|
"offsetExpr": "base + pos_x",
|
||||||
|
"collapsed": false,
|
||||||
|
"refId": "0",
|
||||||
|
"elementKind": "UInt8",
|
||||||
|
"arrayLen": 1,
|
||||||
|
"strLen": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
356
src/examples/EPROCESS.rcx
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
{
|
||||||
|
"baseAddress": "FFFF800000000000",
|
||||||
|
"nextId": "9000",
|
||||||
|
"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":"110","kind":"Struct","name":"single_list_entry","structTypeName":"_SINGLE_LIST_ENTRY","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"111","kind":"Pointer64","name":"Next","offset":0,"parentId":"110","refId":"110","collapsed":true},
|
||||||
|
|
||||||
|
{"id":"120","kind":"Struct","name":"ex_push_lock","structTypeName":"_EX_PUSH_LOCK","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"121","kind":"Hex64","name":"Value","offset":0,"parentId":"120"},
|
||||||
|
|
||||||
|
{"id":"130","kind":"Struct","name":"ex_rundown_ref","structTypeName":"_EX_RUNDOWN_REF","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"131","kind":"Struct","name":"","classKeyword":"union","offset":0,"parentId":"130","refId":"0","collapsed":false},
|
||||||
|
{"id":"132","kind":"UInt64","name":"Count","offset":0,"parentId":"131"},
|
||||||
|
{"id":"133","kind":"Pointer64","name":"Ptr","offset":0,"parentId":"131"},
|
||||||
|
|
||||||
|
{"id":"140","kind":"Struct","name":"ex_fast_ref","structTypeName":"_EX_FAST_REF","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"141","kind":"Struct","name":"","classKeyword":"union","offset":0,"parentId":"140","refId":"0","collapsed":false},
|
||||||
|
{"id":"142","kind":"Pointer64","name":"Object","offset":0,"parentId":"141"},
|
||||||
|
{"id":"143","kind":"UInt64","name":"Value","offset":0,"parentId":"141"},
|
||||||
|
|
||||||
|
{"id":"150","kind":"Struct","name":"unicode_string","structTypeName":"_UNICODE_STRING","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"151","kind":"UInt16","name":"Length","offset":0,"parentId":"150"},
|
||||||
|
{"id":"152","kind":"UInt16","name":"MaximumLength","offset":2,"parentId":"150"},
|
||||||
|
{"id":"153","kind":"Pointer64","name":"Buffer","offset":8,"parentId":"150"},
|
||||||
|
|
||||||
|
{"id":"160","kind":"Struct","name":"large_integer","structTypeName":"_LARGE_INTEGER","classKeyword":"union","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"161","kind":"Struct","name":"","offset":0,"parentId":"160","refId":"0","collapsed":false},
|
||||||
|
{"id":"162","kind":"UInt32","name":"LowPart","offset":0,"parentId":"161"},
|
||||||
|
{"id":"163","kind":"Int32","name":"HighPart","offset":4,"parentId":"161"},
|
||||||
|
{"id":"164","kind":"Int64","name":"QuadPart","offset":0,"parentId":"160"},
|
||||||
|
|
||||||
|
{"id":"170","kind":"Struct","name":"rtl_avl_tree","structTypeName":"_RTL_AVL_TREE","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"171","kind":"Pointer64","name":"Root","offset":0,"parentId":"170"},
|
||||||
|
|
||||||
|
{"id":"180","kind":"Struct","name":"kstack_count","structTypeName":"_KSTACK_COUNT","classKeyword":"union","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"181","kind":"Int32","name":"Value","offset":0,"parentId":"180"},
|
||||||
|
{"id":"182","kind":"Hex32","name":"State:3 StackCount:29","offset":0,"parentId":"180"},
|
||||||
|
|
||||||
|
{"id":"190","kind":"Struct","name":"kexecute_options","structTypeName":"_KEXECUTE_OPTIONS","classKeyword":"union","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"191","kind":"Struct","name":"","offset":0,"parentId":"190","refId":"0","collapsed":false},
|
||||||
|
{"id":"192","kind":"UInt8","name":"ExecuteDisable","offset":0,"parentId":"191"},
|
||||||
|
{"id":"193","kind":"Hex8","name":"ExecuteDisable:1 ExecuteEnable:1 DisableThunkEmulation:1 Permanent:1 ExecuteDispatchEnable:1 ImageDispatchEnable:1 DisableExceptionChainValidation:1 Spare:1","offset":0,"parentId":"191"},
|
||||||
|
{"id":"194","kind":"UInt8","name":"ExecuteOptions","offset":0,"parentId":"190"},
|
||||||
|
|
||||||
|
{"id":"200","kind":"Struct","name":"se_audit_info","structTypeName":"_SE_AUDIT_PROCESS_CREATION_INFO","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"201","kind":"Pointer64","name":"ImageFileName","offset":0,"parentId":"200"},
|
||||||
|
|
||||||
|
{"id":"210","kind":"Struct","name":"ps_protection","structTypeName":"_PS_PROTECTION","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"211","kind":"Struct","name":"","classKeyword":"union","offset":0,"parentId":"210","refId":"0","collapsed":false},
|
||||||
|
{"id":"212","kind":"UInt8","name":"Level","offset":0,"parentId":"211"},
|
||||||
|
{"id":"213","kind":"Hex8","name":"Type:3 Audit:1 Signer:4","offset":0,"parentId":"211"},
|
||||||
|
|
||||||
|
{"id":"220","kind":"Struct","name":"timer_delay","structTypeName":"_PS_INTERLOCKED_TIMER_DELAY_VALUES","classKeyword":"union","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"221","kind":"Hex64","name":"DelayMs:30 CoalescingWindowMs:30 Reserved:1 NewTimerWheel:1 Retry:1 Locked:1","offset":0,"parentId":"220"},
|
||||||
|
{"id":"222","kind":"UInt64","name":"All","offset":0,"parentId":"220"},
|
||||||
|
|
||||||
|
{"id":"230","kind":"Struct","name":"wnf_state_name","structTypeName":"_WNF_STATE_NAME","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"231","kind":"UInt32","name":"Data_0","offset":0,"parentId":"230"},
|
||||||
|
{"id":"232","kind":"UInt32","name":"Data_1","offset":4,"parentId":"230"},
|
||||||
|
|
||||||
|
{"id":"240","kind":"Struct","name":"dynamic_ranges","structTypeName":"_PS_DYNAMIC_ENFORCED_ADDRESS_RANGES","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"241","kind":"Struct","name":"Tree","structTypeName":"_RTL_AVL_TREE","offset":0,"parentId":"240","refId":"170","collapsed":true},
|
||||||
|
{"id":"242","kind":"Struct","name":"Lock","structTypeName":"_EX_PUSH_LOCK","offset":8,"parentId":"240","refId":"120","collapsed":true},
|
||||||
|
|
||||||
|
{"id":"250","kind":"Struct","name":"alpc_context","structTypeName":"_ALPC_PROCESS_CONTEXT","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"251","kind":"Struct","name":"Lock","structTypeName":"_EX_PUSH_LOCK","offset":0,"parentId":"250","refId":"120","collapsed":true},
|
||||||
|
{"id":"252","kind":"Struct","name":"ViewListHead","structTypeName":"_LIST_ENTRY","offset":8,"parentId":"250","refId":"100","collapsed":true},
|
||||||
|
{"id":"253","kind":"UInt64","name":"PagedPoolQuotaCache","offset":24,"parentId":"250"},
|
||||||
|
|
||||||
|
{"id":"260","kind":"Struct","name":"mmsupport_flags","structTypeName":"_MMSUPPORT_FLAGS","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"261","kind":"Hex32","name":"EntireFlags","offset":0,"parentId":"260"},
|
||||||
|
|
||||||
|
{"id":"270","kind":"Struct","name":"mmsupport_shared","structTypeName":"_MMSUPPORT_SHARED","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"271","kind":"Pointer64","name":"WorkingSetLockArray","offset":0,"parentId":"270"},
|
||||||
|
{"id":"272","kind":"UInt64","name":"ReleasedCommitDebt","offset":8,"parentId":"270"},
|
||||||
|
{"id":"273","kind":"UInt64","name":"ResetPagesRepurposedCount","offset":16,"parentId":"270"},
|
||||||
|
{"id":"274","kind":"Pointer64","name":"WsSwapSupport","offset":24,"parentId":"270"},
|
||||||
|
{"id":"275","kind":"Pointer64","name":"CommitReleaseContext","offset":32,"parentId":"270"},
|
||||||
|
{"id":"276","kind":"Pointer64","name":"AccessLog","offset":40,"parentId":"270"},
|
||||||
|
{"id":"277","kind":"UInt64","name":"ChargedWslePages","offset":48,"parentId":"270"},
|
||||||
|
{"id":"278","kind":"UInt64","name":"ActualWslePages","offset":56,"parentId":"270"},
|
||||||
|
{"id":"279","kind":"Int32","name":"WorkingSetCoreLock","offset":64,"parentId":"270"},
|
||||||
|
{"id":"280","kind":"Pointer64","name":"ShadowMapping","offset":72,"parentId":"270"},
|
||||||
|
|
||||||
|
{"id":"300","kind":"Struct","name":"mmsupport_instance","structTypeName":"_MMSUPPORT_INSTANCE","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"301","kind":"UInt32","name":"NextPageColor","offset":0,"parentId":"300"},
|
||||||
|
{"id":"302","kind":"UInt32","name":"PageFaultCount","offset":4,"parentId":"300"},
|
||||||
|
{"id":"303","kind":"UInt64","name":"TrimmedPageCount","offset":8,"parentId":"300"},
|
||||||
|
{"id":"304","kind":"Pointer64","name":"VmWorkingSetList","offset":16,"parentId":"300"},
|
||||||
|
{"id":"305","kind":"Struct","name":"WorkingSetExpansionLinks","structTypeName":"_LIST_ENTRY","offset":24,"parentId":"300","refId":"100","collapsed":true},
|
||||||
|
{"id":"306","kind":"UInt64","name":"AgeDistribution_0","offset":40,"parentId":"300"},
|
||||||
|
{"id":"307","kind":"UInt64","name":"AgeDistribution_1","offset":48,"parentId":"300"},
|
||||||
|
{"id":"308","kind":"UInt64","name":"AgeDistribution_2","offset":56,"parentId":"300"},
|
||||||
|
{"id":"309","kind":"UInt64","name":"AgeDistribution_3","offset":64,"parentId":"300"},
|
||||||
|
{"id":"310","kind":"UInt64","name":"AgeDistribution_4","offset":72,"parentId":"300"},
|
||||||
|
{"id":"311","kind":"UInt64","name":"AgeDistribution_5","offset":80,"parentId":"300"},
|
||||||
|
{"id":"312","kind":"UInt64","name":"AgeDistribution_6","offset":88,"parentId":"300"},
|
||||||
|
{"id":"313","kind":"UInt64","name":"AgeDistribution_7","offset":96,"parentId":"300"},
|
||||||
|
{"id":"314","kind":"Pointer64","name":"ExitOutswapGate","offset":104,"parentId":"300"},
|
||||||
|
{"id":"315","kind":"UInt64","name":"MinimumWorkingSetSize","offset":112,"parentId":"300"},
|
||||||
|
{"id":"316","kind":"UInt64","name":"MaximumWorkingSetSize","offset":120,"parentId":"300"},
|
||||||
|
{"id":"317","kind":"UInt64","name":"WorkingSetLeafSize","offset":128,"parentId":"300"},
|
||||||
|
{"id":"318","kind":"UInt64","name":"WorkingSetLeafPrivateSize","offset":136,"parentId":"300"},
|
||||||
|
{"id":"319","kind":"UInt64","name":"WorkingSetSize","offset":144,"parentId":"300"},
|
||||||
|
{"id":"320","kind":"UInt64","name":"WorkingSetPrivateSize","offset":152,"parentId":"300"},
|
||||||
|
{"id":"321","kind":"UInt64","name":"PeakWorkingSetSize","offset":160,"parentId":"300"},
|
||||||
|
{"id":"322","kind":"UInt32","name":"HardFaultCount","offset":168,"parentId":"300"},
|
||||||
|
{"id":"323","kind":"UInt16","name":"LastTrimStamp","offset":172,"parentId":"300"},
|
||||||
|
{"id":"324","kind":"UInt16","name":"PartitionId","offset":174,"parentId":"300"},
|
||||||
|
{"id":"325","kind":"UInt64","name":"SelfmapLock","offset":176,"parentId":"300"},
|
||||||
|
{"id":"326","kind":"Struct","name":"Flags","structTypeName":"_MMSUPPORT_FLAGS","offset":184,"parentId":"300","refId":"260","collapsed":true},
|
||||||
|
|
||||||
|
{"id":"350","kind":"Struct","name":"mmsupport_full","structTypeName":"_MMSUPPORT_FULL","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"351","kind":"Struct","name":"Instance","structTypeName":"_MMSUPPORT_INSTANCE","offset":0,"parentId":"350","refId":"300","collapsed":true},
|
||||||
|
{"id":"352","kind":"Struct","name":"Shared","structTypeName":"_MMSUPPORT_SHARED","offset":192,"parentId":"350","refId":"270","collapsed":true},
|
||||||
|
|
||||||
|
{"id":"400","kind":"Struct","name":"dispatcher_header","structTypeName":"_DISPATCHER_HEADER","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"401","kind":"Struct","name":"","classKeyword":"union","offset":0,"parentId":"400","refId":"0","collapsed":false},
|
||||||
|
{"id":"402","kind":"UInt8","name":"Type","offset":0,"parentId":"401"},
|
||||||
|
{"id":"403","kind":"UInt8","name":"Signalling","offset":1,"parentId":"401"},
|
||||||
|
{"id":"404","kind":"UInt8","name":"Size","offset":2,"parentId":"401"},
|
||||||
|
{"id":"405","kind":"UInt8","name":"Reserved1","offset":3,"parentId":"401"},
|
||||||
|
{"id":"406","kind":"Int32","name":"Lock","offset":0,"parentId":"401"},
|
||||||
|
{"id":"407","kind":"Int32","name":"SignalState","offset":4,"parentId":"400"},
|
||||||
|
{"id":"408","kind":"Struct","name":"WaitListHead","structTypeName":"_LIST_ENTRY","offset":8,"parentId":"400","refId":"100","collapsed":true},
|
||||||
|
|
||||||
|
{"id":"500","kind":"Struct","name":"kprocess","structTypeName":"_KPROCESS","offset":0,"parentId":"0","refId":"0","collapsed":true},
|
||||||
|
{"id":"501","kind":"Struct","name":"Header","structTypeName":"_DISPATCHER_HEADER","offset":0,"parentId":"500","refId":"400","collapsed":true},
|
||||||
|
{"id":"502","kind":"Struct","name":"ProfileListHead","structTypeName":"_LIST_ENTRY","offset":24,"parentId":"500","refId":"100","collapsed":true},
|
||||||
|
{"id":"503","kind":"UInt64","name":"DirectoryTableBase","offset":40,"parentId":"500"},
|
||||||
|
{"id":"504","kind":"Struct","name":"ThreadListHead","structTypeName":"_LIST_ENTRY","offset":48,"parentId":"500","refId":"100","collapsed":true},
|
||||||
|
{"id":"505","kind":"UInt32","name":"ProcessLock","offset":64,"parentId":"500"},
|
||||||
|
{"id":"506","kind":"UInt32","name":"ProcessTimerDelay","offset":68,"parentId":"500"},
|
||||||
|
{"id":"507","kind":"UInt64","name":"DeepFreezeStartTime","offset":72,"parentId":"500"},
|
||||||
|
{"id":"508","kind":"Pointer64","name":"Affinity","offset":80,"parentId":"500"},
|
||||||
|
{"id":"509","kind":"Hex64","name":"AutoBoostState","offset":88,"parentId":"500"},
|
||||||
|
{"id":"510","kind":"Struct","name":"ReadyListHead","structTypeName":"_LIST_ENTRY","offset":104,"parentId":"500","refId":"100","collapsed":true},
|
||||||
|
{"id":"511","kind":"Struct","name":"SwapListEntry","structTypeName":"_SINGLE_LIST_ENTRY","offset":120,"parentId":"500","refId":"110","collapsed":true},
|
||||||
|
{"id":"512","kind":"Pointer64","name":"ActiveProcessors","offset":128,"parentId":"500"},
|
||||||
|
{"id":"513","kind":"Struct","name":"","classKeyword":"union","offset":136,"parentId":"500","refId":"0","collapsed":false},
|
||||||
|
{"id":"514","kind":"Hex32","name":"AutoAlignment:1 DisableBoost:1 DisableQuantum:1 DeepFreeze:1 TimerVirtualization:1 CheckStackExtents:1 CacheIsolationEnabled:1 PpmPolicy:4 VaSpaceDeleted:1 MultiGroup:1 ForegroundProcess:1 ReservedFlags:18","offset":0,"parentId":"513"},
|
||||||
|
{"id":"515","kind":"Int32","name":"ProcessFlags","offset":0,"parentId":"513"},
|
||||||
|
{"id":"516","kind":"Int8","name":"BasePriority","offset":144,"parentId":"500"},
|
||||||
|
{"id":"517","kind":"Int8","name":"QuantumReset","offset":145,"parentId":"500"},
|
||||||
|
{"id":"518","kind":"Int8","name":"Visited","offset":146,"parentId":"500"},
|
||||||
|
{"id":"519","kind":"Struct","name":"Flags","structTypeName":"_KEXECUTE_OPTIONS","offset":147,"parentId":"500","refId":"190","collapsed":true},
|
||||||
|
{"id":"520","kind":"Struct","name":"StackCount","structTypeName":"_KSTACK_COUNT","offset":264,"parentId":"500","refId":"180","collapsed":true},
|
||||||
|
{"id":"521","kind":"Struct","name":"ProcessListEntry","structTypeName":"_LIST_ENTRY","offset":272,"parentId":"500","refId":"100","collapsed":true},
|
||||||
|
{"id":"522","kind":"UInt64","name":"CycleTime","offset":288,"parentId":"500"},
|
||||||
|
{"id":"523","kind":"UInt64","name":"ContextSwitches","offset":296,"parentId":"500"},
|
||||||
|
{"id":"524","kind":"Pointer64","name":"SchedulingGroup","offset":304,"parentId":"500"},
|
||||||
|
{"id":"525","kind":"UInt64","name":"KernelTime","offset":312,"parentId":"500"},
|
||||||
|
{"id":"526","kind":"UInt64","name":"UserTime","offset":320,"parentId":"500"},
|
||||||
|
{"id":"527","kind":"UInt64","name":"ReadyTime","offset":328,"parentId":"500"},
|
||||||
|
{"id":"528","kind":"UInt32","name":"FreezeCount","offset":336,"parentId":"500"},
|
||||||
|
{"id":"529","kind":"UInt64","name":"UserDirectoryTableBase","offset":344,"parentId":"500"},
|
||||||
|
{"id":"530","kind":"UInt8","name":"AddressPolicy","offset":352,"parentId":"500"},
|
||||||
|
{"id":"531","kind":"Pointer64","name":"InstrumentationCallback","offset":360,"parentId":"500"},
|
||||||
|
{"id":"532","kind":"UInt64","name":"SecureHandle","offset":368,"parentId":"500"},
|
||||||
|
{"id":"533","kind":"UInt64","name":"KernelWaitTime","offset":376,"parentId":"500"},
|
||||||
|
{"id":"534","kind":"UInt64","name":"UserWaitTime","offset":384,"parentId":"500"},
|
||||||
|
{"id":"535","kind":"UInt64","name":"LastRebalanceQpc","offset":392,"parentId":"500"},
|
||||||
|
{"id":"536","kind":"Pointer64","name":"PerProcessorCycleTimes","offset":400,"parentId":"500"},
|
||||||
|
{"id":"537","kind":"UInt64","name":"ExtendedFeatureDisableMask","offset":408,"parentId":"500"},
|
||||||
|
{"id":"538","kind":"UInt16","name":"PrimaryGroup","offset":416,"parentId":"500"},
|
||||||
|
{"id":"539","kind":"Pointer64","name":"UserCetLogging","offset":424,"parentId":"500"},
|
||||||
|
{"id":"540","kind":"Struct","name":"CpuPartitionList","structTypeName":"_LIST_ENTRY","offset":432,"parentId":"500","refId":"100","collapsed":true},
|
||||||
|
{"id":"541","kind":"Pointer64","name":"AvailableCpuState","offset":448,"parentId":"500"},
|
||||||
|
|
||||||
|
{"id":"2000","kind":"Struct","name":"eprocess","structTypeName":"_EPROCESS","offset":0,"parentId":"0","refId":"0","collapsed":false},
|
||||||
|
{"id":"2001","kind":"Struct","name":"Pcb","structTypeName":"_KPROCESS","offset":0,"parentId":"2000","refId":"500","collapsed":true},
|
||||||
|
{"id":"2002","kind":"Struct","name":"ProcessLock","structTypeName":"_EX_PUSH_LOCK","offset":456,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2003","kind":"Pointer64","name":"UniqueProcessId","offset":464,"parentId":"2000"},
|
||||||
|
{"id":"2004","kind":"Struct","name":"ActiveProcessLinks","structTypeName":"_LIST_ENTRY","offset":472,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2005","kind":"Struct","name":"RundownProtect","structTypeName":"_EX_RUNDOWN_REF","offset":488,"parentId":"2000","refId":"130","collapsed":true},
|
||||||
|
{"id":"2006","kind":"Struct","name":"","classKeyword":"union","offset":496,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2007","kind":"UInt32","name":"Flags2","offset":0,"parentId":"2006"},
|
||||||
|
{"id":"2008","kind":"Hex32","name":"JobNotReallyActive:1 AccountingFolded:1 NewProcessReported:1 ExitProcessReported:1 ReportCommitChanges:1 LastReportMemory:1 ForceWakeCharge:1 CrossSessionCreate:1 NeedsHandleRundown:1 RefTraceEnabled:1 PicoCreated:1 EmptyJobEvaluated:1 DefaultPagePriority:3 PrimaryTokenFrozen:1","offset":0,"parentId":"2006"},
|
||||||
|
{"id":"2009","kind":"Struct","name":"","classKeyword":"union","offset":500,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2010","kind":"UInt32","name":"Flags","offset":0,"parentId":"2009"},
|
||||||
|
{"id":"2011","kind":"Hex32","name":"CreateReported:1 NoDebugInherit:1 ProcessExiting:1 ProcessDelete:1 ManageExecutableMemoryWrites:1 VmDeleted:1 OutswapEnabled:1 Outswapped:1 FailFastOnCommitFail:1 Wow64VaSpace4Gb:1 AddressSpaceInitialized:2 SetTimerResolution:1 BreakOnTermination:1","offset":0,"parentId":"2009"},
|
||||||
|
{"id":"2012","kind":"Int64","name":"CreateTime","offset":504,"parentId":"2000"},
|
||||||
|
{"id":"2013","kind":"UInt64","name":"ProcessQuotaUsage_0","offset":512,"parentId":"2000"},
|
||||||
|
{"id":"2014","kind":"UInt64","name":"ProcessQuotaUsage_1","offset":520,"parentId":"2000"},
|
||||||
|
{"id":"2015","kind":"UInt64","name":"ProcessQuotaPeak_0","offset":528,"parentId":"2000"},
|
||||||
|
{"id":"2016","kind":"UInt64","name":"ProcessQuotaPeak_1","offset":536,"parentId":"2000"},
|
||||||
|
{"id":"2017","kind":"UInt64","name":"PeakVirtualSize","offset":544,"parentId":"2000"},
|
||||||
|
{"id":"2018","kind":"UInt64","name":"VirtualSize","offset":552,"parentId":"2000"},
|
||||||
|
{"id":"2019","kind":"Struct","name":"SessionProcessLinks","structTypeName":"_LIST_ENTRY","offset":560,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2020","kind":"Struct","name":"","classKeyword":"union","offset":576,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2021","kind":"Pointer64","name":"ExceptionPortData","offset":0,"parentId":"2020"},
|
||||||
|
{"id":"2022","kind":"UInt64","name":"ExceptionPortValue","offset":0,"parentId":"2020"},
|
||||||
|
{"id":"2023","kind":"Struct","name":"Token","structTypeName":"_EX_FAST_REF","offset":584,"parentId":"2000","refId":"140","collapsed":true},
|
||||||
|
{"id":"2024","kind":"UInt64","name":"MmReserved","offset":592,"parentId":"2000"},
|
||||||
|
{"id":"2025","kind":"Struct","name":"AddressCreationLock","structTypeName":"_EX_PUSH_LOCK","offset":600,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2026","kind":"Struct","name":"PageTableCommitmentLock","structTypeName":"_EX_PUSH_LOCK","offset":608,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2027","kind":"Pointer64","name":"RotateInProgress","offset":616,"parentId":"2000"},
|
||||||
|
{"id":"2028","kind":"Pointer64","name":"ForkInProgress","offset":624,"parentId":"2000"},
|
||||||
|
{"id":"2029","kind":"Pointer64","name":"CommitChargeJob","offset":632,"parentId":"2000"},
|
||||||
|
{"id":"2030","kind":"Struct","name":"CloneRoot","structTypeName":"_RTL_AVL_TREE","offset":640,"parentId":"2000","refId":"170","collapsed":true},
|
||||||
|
{"id":"2031","kind":"UInt64","name":"NumberOfPrivatePages","offset":648,"parentId":"2000"},
|
||||||
|
{"id":"2032","kind":"UInt64","name":"NumberOfLockedPages","offset":656,"parentId":"2000"},
|
||||||
|
{"id":"2033","kind":"Pointer64","name":"Win32Process","offset":664,"parentId":"2000"},
|
||||||
|
{"id":"2034","kind":"Pointer64","name":"Job","offset":672,"parentId":"2000"},
|
||||||
|
{"id":"2035","kind":"Pointer64","name":"SectionObject","offset":680,"parentId":"2000"},
|
||||||
|
{"id":"2036","kind":"Pointer64","name":"SectionBaseAddress","offset":688,"parentId":"2000"},
|
||||||
|
{"id":"2037","kind":"UInt32","name":"Cookie","offset":696,"parentId":"2000"},
|
||||||
|
{"id":"2038","kind":"Pointer64","name":"WorkingSetWatch","offset":704,"parentId":"2000"},
|
||||||
|
{"id":"2039","kind":"Pointer64","name":"Win32WindowStation","offset":712,"parentId":"2000"},
|
||||||
|
{"id":"2040","kind":"Pointer64","name":"InheritedFromUniqueProcessId","offset":720,"parentId":"2000"},
|
||||||
|
{"id":"2041","kind":"UInt64","name":"OwnerProcessId","offset":728,"parentId":"2000"},
|
||||||
|
{"id":"2042","kind":"Pointer64","name":"Peb","offset":736,"parentId":"2000"},
|
||||||
|
{"id":"2043","kind":"Pointer64","name":"Session","offset":744,"parentId":"2000"},
|
||||||
|
{"id":"2044","kind":"Pointer64","name":"Spare1","offset":752,"parentId":"2000"},
|
||||||
|
{"id":"2045","kind":"Pointer64","name":"QuotaBlock","offset":760,"parentId":"2000"},
|
||||||
|
{"id":"2046","kind":"Pointer64","name":"ObjectTable","offset":768,"parentId":"2000"},
|
||||||
|
{"id":"2047","kind":"Pointer64","name":"DebugPort","offset":776,"parentId":"2000"},
|
||||||
|
{"id":"2048","kind":"Pointer64","name":"WoW64Process","offset":784,"parentId":"2000"},
|
||||||
|
{"id":"2049","kind":"Struct","name":"DeviceMap","structTypeName":"_EX_FAST_REF","offset":792,"parentId":"2000","refId":"140","collapsed":true},
|
||||||
|
{"id":"2050","kind":"Pointer64","name":"EtwDataSource","offset":800,"parentId":"2000"},
|
||||||
|
{"id":"2051","kind":"UInt64","name":"PageDirectoryPte","offset":808,"parentId":"2000"},
|
||||||
|
{"id":"2052","kind":"Pointer64","name":"ImageFilePointer","offset":816,"parentId":"2000"},
|
||||||
|
{"id":"2053","kind":"Hex64","name":"ImageFileName_lo","offset":824,"parentId":"2000"},
|
||||||
|
{"id":"2054","kind":"Hex32","name":"ImageFileName_mi","offset":832,"parentId":"2000"},
|
||||||
|
{"id":"2055","kind":"Hex16","name":"ImageFileName_hi","offset":836,"parentId":"2000"},
|
||||||
|
{"id":"2056","kind":"UInt8","name":"ImageFileName_14","offset":838,"parentId":"2000"},
|
||||||
|
{"id":"2057","kind":"UInt8","name":"PriorityClass","offset":839,"parentId":"2000"},
|
||||||
|
{"id":"2058","kind":"Pointer64","name":"SecurityPort","offset":840,"parentId":"2000"},
|
||||||
|
{"id":"2059","kind":"Struct","name":"SeAuditProcessCreationInfo","structTypeName":"_SE_AUDIT_PROCESS_CREATION_INFO","offset":848,"parentId":"2000","refId":"200","collapsed":true},
|
||||||
|
{"id":"2060","kind":"Struct","name":"JobLinks","structTypeName":"_LIST_ENTRY","offset":856,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2061","kind":"Pointer64","name":"HighestUserAddress","offset":872,"parentId":"2000"},
|
||||||
|
{"id":"2062","kind":"Struct","name":"ThreadListHead","structTypeName":"_LIST_ENTRY","offset":880,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2063","kind":"UInt32","name":"ActiveThreads","offset":896,"parentId":"2000"},
|
||||||
|
{"id":"2064","kind":"UInt32","name":"ImagePathHash","offset":900,"parentId":"2000"},
|
||||||
|
{"id":"2065","kind":"UInt32","name":"DefaultHardErrorProcessing","offset":904,"parentId":"2000"},
|
||||||
|
{"id":"2066","kind":"Int32","name":"LastThreadExitStatus","offset":908,"parentId":"2000"},
|
||||||
|
{"id":"2067","kind":"Struct","name":"PrefetchTrace","structTypeName":"_EX_FAST_REF","offset":912,"parentId":"2000","refId":"140","collapsed":true},
|
||||||
|
{"id":"2068","kind":"Pointer64","name":"LockedPagesList","offset":920,"parentId":"2000"},
|
||||||
|
{"id":"2069","kind":"Int64","name":"ReadOperationCount","offset":928,"parentId":"2000"},
|
||||||
|
{"id":"2070","kind":"Int64","name":"WriteOperationCount","offset":936,"parentId":"2000"},
|
||||||
|
{"id":"2071","kind":"Int64","name":"OtherOperationCount","offset":944,"parentId":"2000"},
|
||||||
|
{"id":"2072","kind":"Int64","name":"ReadTransferCount","offset":952,"parentId":"2000"},
|
||||||
|
{"id":"2073","kind":"Int64","name":"WriteTransferCount","offset":960,"parentId":"2000"},
|
||||||
|
{"id":"2074","kind":"Int64","name":"OtherTransferCount","offset":968,"parentId":"2000"},
|
||||||
|
{"id":"2075","kind":"UInt64","name":"CommitChargeLimit","offset":976,"parentId":"2000"},
|
||||||
|
{"id":"2076","kind":"UInt64","name":"CommitCharge","offset":984,"parentId":"2000"},
|
||||||
|
{"id":"2077","kind":"UInt64","name":"CommitChargePeak","offset":992,"parentId":"2000"},
|
||||||
|
{"id":"2078","kind":"Struct","name":"Vm","structTypeName":"_MMSUPPORT_FULL","offset":1024,"parentId":"2000","refId":"350","collapsed":true},
|
||||||
|
{"id":"2079","kind":"Struct","name":"MmProcessLinks","structTypeName":"_LIST_ENTRY","offset":1344,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2080","kind":"UInt32","name":"ModifiedPageCount","offset":1360,"parentId":"2000"},
|
||||||
|
{"id":"2081","kind":"Int32","name":"ExitStatus","offset":1364,"parentId":"2000"},
|
||||||
|
{"id":"2082","kind":"Struct","name":"VadRoot","structTypeName":"_RTL_AVL_TREE","offset":1368,"parentId":"2000","refId":"170","collapsed":true},
|
||||||
|
{"id":"2083","kind":"Pointer64","name":"VadHint","offset":1376,"parentId":"2000"},
|
||||||
|
{"id":"2084","kind":"UInt64","name":"VadCount","offset":1384,"parentId":"2000"},
|
||||||
|
{"id":"2085","kind":"UInt64","name":"VadPhysicalPages","offset":1392,"parentId":"2000"},
|
||||||
|
{"id":"2086","kind":"UInt64","name":"VadPhysicalPagesLimit","offset":1400,"parentId":"2000"},
|
||||||
|
{"id":"2087","kind":"Struct","name":"AlpcContext","structTypeName":"_ALPC_PROCESS_CONTEXT","offset":1408,"parentId":"2000","refId":"250","collapsed":true},
|
||||||
|
{"id":"2088","kind":"Struct","name":"TimerResolutionLink","structTypeName":"_LIST_ENTRY","offset":1440,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2089","kind":"Pointer64","name":"TimerResolutionStackRecord","offset":1456,"parentId":"2000"},
|
||||||
|
{"id":"2090","kind":"UInt32","name":"RequestedTimerResolution","offset":1464,"parentId":"2000"},
|
||||||
|
{"id":"2091","kind":"UInt32","name":"SmallestTimerResolution","offset":1468,"parentId":"2000"},
|
||||||
|
{"id":"2092","kind":"Int64","name":"ExitTime","offset":1472,"parentId":"2000"},
|
||||||
|
{"id":"2093","kind":"Pointer64","name":"InvertedFunctionTable","offset":1480,"parentId":"2000"},
|
||||||
|
{"id":"2094","kind":"Struct","name":"InvertedFunctionTableLock","structTypeName":"_EX_PUSH_LOCK","offset":1488,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2095","kind":"UInt32","name":"ActiveThreadsHighWatermark","offset":1496,"parentId":"2000"},
|
||||||
|
{"id":"2096","kind":"UInt32","name":"LargePrivateVadCount","offset":1500,"parentId":"2000"},
|
||||||
|
{"id":"2097","kind":"Struct","name":"ThreadListLock","structTypeName":"_EX_PUSH_LOCK","offset":1504,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2098","kind":"Pointer64","name":"WnfContext","offset":1512,"parentId":"2000"},
|
||||||
|
{"id":"2099","kind":"Pointer64","name":"ServerSilo","offset":1520,"parentId":"2000"},
|
||||||
|
{"id":"2100","kind":"UInt8","name":"SignatureLevel","offset":1528,"parentId":"2000"},
|
||||||
|
{"id":"2101","kind":"UInt8","name":"SectionSignatureLevel","offset":1529,"parentId":"2000"},
|
||||||
|
{"id":"2102","kind":"Struct","name":"Protection","structTypeName":"_PS_PROTECTION","offset":1530,"parentId":"2000","refId":"210","collapsed":true},
|
||||||
|
{"id":"2103","kind":"Hex8","name":"HangCount:3 GhostCount:3 PrefilterException:1","offset":1531,"parentId":"2000"},
|
||||||
|
{"id":"2104","kind":"Struct","name":"","classKeyword":"union","offset":1532,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2105","kind":"UInt32","name":"Flags3","offset":0,"parentId":"2104"},
|
||||||
|
{"id":"2106","kind":"Hex32","name":"Minimal:1 ReplacingPageRoot:1 Crashed:1 JobVadsAreTracked:1 VadTrackingDisabled:1 AuxiliaryProcess:1 SubsystemProcess:1 IndirectCpuSets:1 RelinquishedCommit:1 HighGraphicsPriority:1 CommitFailLogged:1 ReserveFailLogged:1 SystemProcess:1","offset":0,"parentId":"2104"},
|
||||||
|
{"id":"2107","kind":"Int32","name":"DeviceAsid","offset":1536,"parentId":"2000"},
|
||||||
|
{"id":"2108","kind":"Pointer64","name":"SvmData","offset":1544,"parentId":"2000"},
|
||||||
|
{"id":"2109","kind":"Struct","name":"SvmProcessLock","structTypeName":"_EX_PUSH_LOCK","offset":1552,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2110","kind":"UInt64","name":"SvmLock","offset":1560,"parentId":"2000"},
|
||||||
|
{"id":"2111","kind":"Struct","name":"SvmProcessDeviceListHead","structTypeName":"_LIST_ENTRY","offset":1568,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2112","kind":"UInt64","name":"LastFreezeInterruptTime","offset":1584,"parentId":"2000"},
|
||||||
|
{"id":"2113","kind":"Pointer64","name":"DiskCounters","offset":1592,"parentId":"2000"},
|
||||||
|
{"id":"2114","kind":"Pointer64","name":"PicoContext","offset":1600,"parentId":"2000"},
|
||||||
|
{"id":"2115","kind":"Pointer64","name":"EnclaveTable","offset":1608,"parentId":"2000"},
|
||||||
|
{"id":"2116","kind":"UInt64","name":"EnclaveNumber","offset":1616,"parentId":"2000"},
|
||||||
|
{"id":"2117","kind":"Struct","name":"EnclaveLock","structTypeName":"_EX_PUSH_LOCK","offset":1624,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2118","kind":"UInt32","name":"HighPriorityFaultsAllowed","offset":1632,"parentId":"2000"},
|
||||||
|
{"id":"2119","kind":"Pointer64","name":"EnergyContext","offset":1640,"parentId":"2000"},
|
||||||
|
{"id":"2120","kind":"Pointer64","name":"VmContext","offset":1648,"parentId":"2000"},
|
||||||
|
{"id":"2121","kind":"UInt64","name":"SequenceNumber","offset":1656,"parentId":"2000"},
|
||||||
|
{"id":"2122","kind":"UInt64","name":"CreateInterruptTime","offset":1664,"parentId":"2000"},
|
||||||
|
{"id":"2123","kind":"UInt64","name":"CreateUnbiasedInterruptTime","offset":1672,"parentId":"2000"},
|
||||||
|
{"id":"2124","kind":"UInt64","name":"TotalUnbiasedFrozenTime","offset":1680,"parentId":"2000"},
|
||||||
|
{"id":"2125","kind":"UInt64","name":"LastAppStateUpdateTime","offset":1688,"parentId":"2000"},
|
||||||
|
{"id":"2126","kind":"Hex64","name":"LastAppStateUptime:61 LastAppState:3","offset":1696,"parentId":"2000"},
|
||||||
|
{"id":"2127","kind":"UInt64","name":"SharedCommitCharge","offset":1704,"parentId":"2000"},
|
||||||
|
{"id":"2128","kind":"Struct","name":"SharedCommitLock","structTypeName":"_EX_PUSH_LOCK","offset":1712,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2129","kind":"Struct","name":"SharedCommitLinks","structTypeName":"_LIST_ENTRY","offset":1720,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2130","kind":"UInt64","name":"AllowedCpuSets","offset":1736,"parentId":"2000"},
|
||||||
|
{"id":"2131","kind":"UInt64","name":"DefaultCpuSets","offset":1744,"parentId":"2000"},
|
||||||
|
{"id":"2132","kind":"Pointer64","name":"DiskIoAttribution","offset":1752,"parentId":"2000"},
|
||||||
|
{"id":"2133","kind":"Pointer64","name":"DxgProcess","offset":1760,"parentId":"2000"},
|
||||||
|
{"id":"2134","kind":"UInt32","name":"Win32KFilterSet","offset":1768,"parentId":"2000"},
|
||||||
|
{"id":"2135","kind":"UInt16","name":"Machine","offset":1772,"parentId":"2000"},
|
||||||
|
{"id":"2136","kind":"UInt8","name":"MmSlabIdentity","offset":1774,"parentId":"2000"},
|
||||||
|
{"id":"2137","kind":"UInt8","name":"Spare0","offset":1775,"parentId":"2000"},
|
||||||
|
{"id":"2138","kind":"Struct","name":"ProcessTimerDelay","structTypeName":"_PS_INTERLOCKED_TIMER_DELAY_VALUES","offset":1776,"parentId":"2000","refId":"220","collapsed":true},
|
||||||
|
{"id":"2139","kind":"UInt32","name":"KTimerSets","offset":1784,"parentId":"2000"},
|
||||||
|
{"id":"2140","kind":"UInt32","name":"KTimer2Sets","offset":1788,"parentId":"2000"},
|
||||||
|
{"id":"2141","kind":"UInt32","name":"ThreadTimerSets","offset":1792,"parentId":"2000"},
|
||||||
|
{"id":"2142","kind":"UInt64","name":"VirtualTimerListLock","offset":1800,"parentId":"2000"},
|
||||||
|
{"id":"2143","kind":"Struct","name":"VirtualTimerListHead","structTypeName":"_LIST_ENTRY","offset":1808,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2144","kind":"Struct","name":"WakeChannel","structTypeName":"_WNF_STATE_NAME","offset":1824,"parentId":"2000","refId":"230","collapsed":true},
|
||||||
|
{"id":"2145","kind":"Struct","name":"","classKeyword":"union","offset":1872,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2146","kind":"UInt32","name":"MitigationFlags","offset":0,"parentId":"2145"},
|
||||||
|
{"id":"2147","kind":"Hex32","name":"ControlFlowGuardEnabled:1 ControlFlowGuardExportSuppressionEnabled:1 ControlFlowGuardStrict:1 DisallowStrippedImages:1 ForceRelocateImages:1 HighEntropyASLREnabled:1 StackRandomizationDisabled:1 ExtensionPointDisable:1 DisableDynamicCode:1","offset":0,"parentId":"2145"},
|
||||||
|
{"id":"2148","kind":"Struct","name":"","classKeyword":"union","offset":1876,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2149","kind":"UInt32","name":"MitigationFlags2","offset":0,"parentId":"2148"},
|
||||||
|
{"id":"2150","kind":"Hex32","name":"EnableExportAddressFilter:1 AuditExportAddressFilter:1 EnableRopStackPivot:1 AuditRopStackPivot:1 CetUserShadowStacks:1 SpeculativeStoreBypassDisable:1","offset":0,"parentId":"2148"},
|
||||||
|
{"id":"2151","kind":"Pointer64","name":"PartitionObject","offset":1880,"parentId":"2000"},
|
||||||
|
{"id":"2152","kind":"UInt64","name":"SecurityDomain","offset":1888,"parentId":"2000"},
|
||||||
|
{"id":"2153","kind":"UInt64","name":"ParentSecurityDomain","offset":1896,"parentId":"2000"},
|
||||||
|
{"id":"2154","kind":"Pointer64","name":"CoverageSamplerContext","offset":1904,"parentId":"2000"},
|
||||||
|
{"id":"2155","kind":"Pointer64","name":"MmHotPatchContext","offset":1912,"parentId":"2000"},
|
||||||
|
{"id":"2156","kind":"Struct","name":"DynamicEHContinuationTargetsTree","structTypeName":"_RTL_AVL_TREE","offset":1920,"parentId":"2000","refId":"170","collapsed":true},
|
||||||
|
{"id":"2157","kind":"Struct","name":"DynamicEHContinuationTargetsLock","structTypeName":"_EX_PUSH_LOCK","offset":1928,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2158","kind":"Struct","name":"DynamicEnforcedCetCompatibleRanges","structTypeName":"_PS_DYNAMIC_ENFORCED_ADDRESS_RANGES","offset":1936,"parentId":"2000","refId":"240","collapsed":true},
|
||||||
|
{"id":"2159","kind":"UInt32","name":"DisabledComponentFlags","offset":1952,"parentId":"2000"},
|
||||||
|
{"id":"2160","kind":"Int32","name":"PageCombineSequence","offset":1956,"parentId":"2000"},
|
||||||
|
{"id":"2161","kind":"Struct","name":"EnableOptionalXStateFeaturesLock","structTypeName":"_EX_PUSH_LOCK","offset":1960,"parentId":"2000","refId":"120","collapsed":true},
|
||||||
|
{"id":"2162","kind":"Pointer64","name":"PathRedirectionHashes","offset":1968,"parentId":"2000"},
|
||||||
|
{"id":"2163","kind":"Pointer64","name":"SyscallProvider","offset":1976,"parentId":"2000"},
|
||||||
|
{"id":"2164","kind":"Struct","name":"SyscallProviderProcessLinks","structTypeName":"_LIST_ENTRY","offset":1984,"parentId":"2000","refId":"100","collapsed":true},
|
||||||
|
{"id":"2165","kind":"Hex64","name":"SyscallProviderDispatchContext","offset":2000,"parentId":"2000"},
|
||||||
|
{"id":"2166","kind":"Struct","name":"","classKeyword":"union","offset":2008,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2167","kind":"UInt32","name":"MitigationFlags3","offset":0,"parentId":"2166"},
|
||||||
|
{"id":"2168","kind":"Hex32","name":"RestrictCoreSharing:1 DisallowFsctlSystemCalls:1 AuditDisallowFsctlSystemCalls:1 MitigationFlags3Spare:29","offset":0,"parentId":"2166"},
|
||||||
|
{"id":"2169","kind":"Struct","name":"","classKeyword":"union","offset":2012,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2170","kind":"UInt32","name":"Flags4","offset":0,"parentId":"2169"},
|
||||||
|
{"id":"2171","kind":"Hex32","name":"ThreadWasActive:1 MinimalTerminate:1 ImageExpansionDisable:1 SessionFirstProcess:1","offset":0,"parentId":"2169"},
|
||||||
|
{"id":"2172","kind":"Struct","name":"","classKeyword":"union","offset":2016,"parentId":"2000","refId":"0","collapsed":true},
|
||||||
|
{"id":"2173","kind":"UInt32","name":"SyscallUsage","offset":0,"parentId":"2172"},
|
||||||
|
{"id":"2174","kind":"Hex32","name":"SystemModuleInformation:1 SystemModuleInformationEx:1 SystemLocksInformation:1 SystemHandleInformation:1 SystemExtendedHandleInformation:1","offset":0,"parentId":"2172"},
|
||||||
|
{"id":"2175","kind":"Int32","name":"SupervisorDeviceAsid","offset":2020,"parentId":"2000"},
|
||||||
|
{"id":"2176","kind":"Pointer64","name":"SupervisorSvmData","offset":2024,"parentId":"2000"},
|
||||||
|
{"id":"2177","kind":"Pointer64","name":"NetworkCounters","offset":2032,"parentId":"2000"},
|
||||||
|
{"id":"2178","kind":"Hex64","name":"Execution","offset":2040,"parentId":"2000"},
|
||||||
|
{"id":"2179","kind":"Pointer64","name":"ThreadIndexTable","offset":2048,"parentId":"2000"},
|
||||||
|
{"id":"2180","kind":"Struct","name":"FreezeWorkLinks","structTypeName":"_LIST_ENTRY","offset":2056,"parentId":"2000","refId":"100","collapsed":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
1316
src/examples/KUSER_SHARED_DATA.rcx
Normal file
616
src/examples/MMPFN.rcx
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
160984
src/examples/Vergilius_25H2.rcx
Normal file
@@ -1,344 +0,0 @@
|
|||||||
{
|
|
||||||
"baseAddress": "400000",
|
|
||||||
"nextId": "29",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "1",
|
|
||||||
"kind": "Struct",
|
|
||||||
"name": "aBall",
|
|
||||||
"offset": 0,
|
|
||||||
"parentId": "0",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64,
|
|
||||||
"structTypeName": "ball"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "2",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_00",
|
|
||||||
"offset": 0,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "3",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_08",
|
|
||||||
"offset": 8,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "4",
|
|
||||||
"kind": "Vec4",
|
|
||||||
"name": "position",
|
|
||||||
"offset": 16,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "5",
|
|
||||||
"kind": "Vec3",
|
|
||||||
"name": "velocity",
|
|
||||||
"offset": 32,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "6",
|
|
||||||
"kind": "Hex32",
|
|
||||||
"name": "field_2C",
|
|
||||||
"offset": 44,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "7",
|
|
||||||
"kind": "Float",
|
|
||||||
"name": "speed",
|
|
||||||
"offset": 48,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "8",
|
|
||||||
"kind": "UInt32",
|
|
||||||
"name": "color",
|
|
||||||
"offset": 52,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "9",
|
|
||||||
"kind": "Float",
|
|
||||||
"name": "radius",
|
|
||||||
"offset": 56,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "10",
|
|
||||||
"kind": "Hex32",
|
|
||||||
"name": "field_3C",
|
|
||||||
"offset": 60,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "11",
|
|
||||||
"kind": "Float",
|
|
||||||
"name": "mass",
|
|
||||||
"offset": 64,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "12",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_44",
|
|
||||||
"offset": 68,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "13",
|
|
||||||
"kind": "Bool",
|
|
||||||
"name": "bouncy",
|
|
||||||
"offset": 76,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "14",
|
|
||||||
"kind": "Hex8",
|
|
||||||
"name": "field_4D",
|
|
||||||
"offset": 77,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "15",
|
|
||||||
"kind": "Hex16",
|
|
||||||
"name": "field_4E",
|
|
||||||
"offset": 78,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "16",
|
|
||||||
"kind": "UInt32",
|
|
||||||
"name": "color",
|
|
||||||
"offset": 80,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "17",
|
|
||||||
"kind": "Hex32",
|
|
||||||
"name": "field_54",
|
|
||||||
"offset": 84,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "18",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_58",
|
|
||||||
"offset": 88,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "19",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_60",
|
|
||||||
"offset": 96,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "20",
|
|
||||||
"kind": "Struct",
|
|
||||||
"name": "aPhysics",
|
|
||||||
"offset": 0,
|
|
||||||
"parentId": "0",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64,
|
|
||||||
"structTypeName": "Physics"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "21",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_00",
|
|
||||||
"offset": 0,
|
|
||||||
"parentId": "20",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "22",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_08",
|
|
||||||
"offset": 8,
|
|
||||||
"parentId": "20",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "23",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_10",
|
|
||||||
"offset": 16,
|
|
||||||
"parentId": "20",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "24",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_18",
|
|
||||||
"offset": 24,
|
|
||||||
"parentId": "20",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "25",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_20",
|
|
||||||
"offset": 32,
|
|
||||||
"parentId": "20",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": true,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "26",
|
|
||||||
"kind": "Pointer64",
|
|
||||||
"name": "physics",
|
|
||||||
"offset": 104,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "20",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "27",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_70",
|
|
||||||
"offset": 112,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"arrayLen": 1,
|
|
||||||
"collapsed": false,
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"id": "28",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "field_78",
|
|
||||||
"offset": 120,
|
|
||||||
"parentId": "1",
|
|
||||||
"refId": "0",
|
|
||||||
"strLen": 64
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
10755
src/examples/t6zm.rcx
Normal file
BIN
src/fonts/JetBrainsMono.ttf
Normal file
274
src/format.cpp
@@ -1,4 +1,6 @@
|
|||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
#include "addressparser.h"
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
@@ -21,6 +23,14 @@ static QString fit(QString s, int w) {
|
|||||||
return s.leftJustified(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 ──
|
// ── Type name ──
|
||||||
|
|
||||||
// Override seam: injectable type-name provider
|
// Override seam: injectable type-name provider
|
||||||
@@ -41,13 +51,15 @@ QString typeName(NodeKind kind, int colType) {
|
|||||||
return fit(m ? QString::fromLatin1(m->typeName) : QStringLiteral("???"), colType);
|
return fit(m ? QString::fromLatin1(m->typeName) : QStringLiteral("???"), colType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array type string: "uint32_t[16]" or "char[64]"
|
// Array type string: "uint32_t[16]" or "Material[2]"
|
||||||
QString arrayTypeName(NodeKind elemKind, int count) {
|
QString arrayTypeName(NodeKind elemKind, int count, const QString& structName) {
|
||||||
auto* m = kindMeta(elemKind);
|
QString elem;
|
||||||
QString elem = m ? QString::fromLatin1(m->typeName) : QStringLiteral("???");
|
if (elemKind == NodeKind::Struct && !structName.isEmpty())
|
||||||
// char[] for UInt8, wchar_t[] for UInt16
|
elem = structName;
|
||||||
if (elemKind == NodeKind::UInt8) elem = QStringLiteral("char");
|
else {
|
||||||
else if (elemKind == NodeKind::UInt16) elem = QStringLiteral("wchar_t");
|
auto* m = kindMeta(elemKind);
|
||||||
|
elem = m ? QString::fromLatin1(m->typeName) : QStringLiteral("???");
|
||||||
|
}
|
||||||
return elem + QStringLiteral("[") + QString::number(count) + QStringLiteral("]");
|
return elem + QStringLiteral("[") + QString::number(count) + QStringLiteral("]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,22 +80,38 @@ static QString rawHex(uint64_t v, int digits) {
|
|||||||
return QString::number(v, 16).rightJustified(digits, '0');
|
return QString::number(v, 16).rightJustified(digits, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
QString fmtInt8(int8_t v) { return hexVal((uint8_t)v); }
|
QString fmtInt8(int8_t v) { return QString::number(v); }
|
||||||
QString fmtInt16(int16_t v) { return hexVal((uint16_t)v); }
|
QString fmtInt16(int16_t v) { return QString::number(v); }
|
||||||
QString fmtInt32(int32_t v) { return hexVal((uint32_t)v); }
|
QString fmtInt32(int32_t v) { return QString::number(v); }
|
||||||
QString fmtInt64(int64_t v) { return hexVal((uint64_t)v); }
|
QString fmtInt64(int64_t v) { return QString::number((qlonglong)v); }
|
||||||
QString fmtUInt8(uint8_t v) { return hexVal(v); }
|
QString fmtUInt8(uint8_t v) { return hexVal(v); }
|
||||||
QString fmtUInt16(uint16_t v) { return hexVal(v); }
|
QString fmtUInt16(uint16_t v) { return hexVal(v); }
|
||||||
QString fmtUInt32(uint32_t v) { return hexVal(v); }
|
QString fmtUInt32(uint32_t v) { return hexVal(v); }
|
||||||
QString fmtUInt64(uint64_t v) { return hexVal(v); }
|
QString fmtUInt64(uint64_t v) { return hexVal(v); }
|
||||||
|
|
||||||
QString fmtFloat(float v) {
|
QString fmtFloat(float v) {
|
||||||
QString s = QString::number(v, 'g', 4);
|
// Fixed 7-char body: digits + "." + decimals + "f"
|
||||||
if (!s.contains('.') && !s.contains('e') && !s.contains('E'))
|
// Negative values get a '-' prefix (8 chars total), positive stay 7.
|
||||||
s += QStringLiteral(".f");
|
if (std::isnan(v)) return QStringLiteral("NaN");
|
||||||
else
|
if (std::isinf(v)) return v > 0 ? QStringLiteral("inff") : QStringLiteral("-inff");
|
||||||
s += QLatin1Char('f');
|
|
||||||
return s;
|
float av = std::fabs(v);
|
||||||
|
if (av >= 100000.f)
|
||||||
|
return v < 0 ? QStringLiteral("-99999+f") : QStringLiteral("99999+f");
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounding pushed past 99999 — use overflow cap
|
||||||
|
return v < 0 ? QStringLiteral("-99999+f") : QStringLiteral("99999+f");
|
||||||
}
|
}
|
||||||
QString fmtDouble(double v) {
|
QString fmtDouble(double v) {
|
||||||
QString s = QString::number(v, 'g', 6);
|
QString s = QString::number(v, 'g', 6);
|
||||||
@@ -93,15 +121,8 @@ QString fmtDouble(double v) {
|
|||||||
}
|
}
|
||||||
QString fmtBool(uint8_t v) { return v ? QStringLiteral("true") : QStringLiteral("false"); }
|
QString fmtBool(uint8_t v) { return v ? QStringLiteral("true") : QStringLiteral("false"); }
|
||||||
|
|
||||||
QString fmtPointer32(uint32_t v) {
|
QString fmtPointer32(uint32_t v) { return hexVal(v); }
|
||||||
if (v == 0) return QStringLiteral("-> NULL");
|
QString fmtPointer64(uint64_t v) { return hexVal(v); }
|
||||||
return QStringLiteral("-> ") + hexVal(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString fmtPointer64(uint64_t v) {
|
|
||||||
if (v == 0) return QStringLiteral("-> NULL");
|
|
||||||
return QStringLiteral("-> ") + hexVal(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Indentation ──
|
// ── Indentation ──
|
||||||
|
|
||||||
@@ -111,28 +132,34 @@ QString indent(int depth) {
|
|||||||
|
|
||||||
// ── Offset margin ──
|
// ── Offset margin ──
|
||||||
|
|
||||||
QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation) {
|
QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDigits) {
|
||||||
if (isContinuation) return QStringLiteral(" \u00B7 ");
|
if (isContinuation) return QStringLiteral(" \u00B7 ");
|
||||||
return QString::number(absoluteOffset, 16).toUpper() + QChar(' ');
|
return QString::number(absoluteOffset, 16).toUpper()
|
||||||
|
.rightJustified(hexDigits, '0') + QChar(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Struct type name (for width calculation) ──
|
// ── Struct type name (for width calculation) ──
|
||||||
|
|
||||||
QString structTypeName(const Node& node) {
|
QString structTypeName(const Node& node) {
|
||||||
// Full type string: "struct TypeName" or just "struct" if no typename
|
// Named types: just the type name (e.g. "_LIST_ENTRY")
|
||||||
QString base = typeName(node.kind).trimmed(); // "struct"
|
// Anonymous: just the keyword (e.g. "union", "struct")
|
||||||
if (!node.structTypeName.isEmpty())
|
if (!node.structTypeName.isEmpty())
|
||||||
return base + QStringLiteral(" ") + node.structTypeName;
|
return node.structTypeName;
|
||||||
return base;
|
return node.resolvedClassKeyword();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Struct header / footer ──
|
// ── 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)
|
// Columnar format: <type> <name> { (or no brace when collapsed)
|
||||||
QString ind = indent(depth);
|
QString ind = indent(depth);
|
||||||
QString type = fit(structTypeName(node), colType);
|
QString rawType = structTypeName(node);
|
||||||
QString suffix = collapsed ? QString() : QStringLiteral("{");
|
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;
|
return ind + type + SEP + node.name + SEP + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +169,10 @@ QString fmtStructFooter(const Node& /*node*/, int depth, int /*totalSize*/) {
|
|||||||
|
|
||||||
// ── Array header ──
|
// ── Array header ──
|
||||||
// Columnar format: <type[count]> <name> { (or no brace when collapsed)
|
// 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) {
|
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 ind = indent(depth);
|
||||||
QString type = fit(arrayTypeName(node.elementKind, node.arrayLen), colType);
|
QString rawType = arrayTypeName(node.elementKind, node.arrayLen, elemStructName);
|
||||||
|
QString type = compact ? fitOverflow(rawType, colType) : fit(rawType, colType);
|
||||||
QString suffix = collapsed ? QString() : QStringLiteral("{");
|
QString suffix = collapsed ? QString() : QStringLiteral("{");
|
||||||
return ind + type + SEP + node.name + SEP + suffix;
|
return ind + type + SEP + node.name + SEP + suffix;
|
||||||
}
|
}
|
||||||
@@ -153,10 +181,16 @@ QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collap
|
|||||||
|
|
||||||
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
||||||
const Provider& prov, uint64_t addr,
|
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 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 (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)
|
// Collapsed: show pointer value instead of brace (name padded for value alignment)
|
||||||
QString name = fit(node.name, colName);
|
QString name = fit(node.name, colName);
|
||||||
QString val = fit(readValue(node, prov, addr, 0), COL_VALUE);
|
QString val = fit(readValue(node, prov, addr, 0), COL_VALUE);
|
||||||
@@ -242,15 +276,55 @@ static QString readValueImpl(const Node& node, const Provider& prov,
|
|||||||
if (!display) return rawHex(val, 8);
|
if (!display) return rawHex(val, 8);
|
||||||
QString s = fmtPointer32(val);
|
QString s = fmtPointer32(val);
|
||||||
QString sym = prov.getSymbol((uint64_t)val);
|
QString sym = prov.getSymbol((uint64_t)val);
|
||||||
if (!sym.isEmpty()) s += QStringLiteral(" ") + sym;
|
if (!sym.isEmpty()) s += QStringLiteral(" // ") + sym;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
case NodeKind::Pointer64: {
|
case NodeKind::Pointer64: {
|
||||||
uint64_t val = prov.readU64(addr);
|
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);
|
if (!display) return rawHex(val, 16);
|
||||||
QString s = fmtPointer64(val);
|
QString s = fmtPointer64(val);
|
||||||
QString sym = prov.getSymbol(val);
|
QString sym = prov.getSymbol(val);
|
||||||
if (!sym.isEmpty()) s += QStringLiteral(" ") + sym;
|
if (!sym.isEmpty()) s += QStringLiteral(" // ") + sym;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
case NodeKind::FuncPtr32: {
|
||||||
|
uint32_t val = prov.readU32(addr);
|
||||||
|
if (!display) return rawHex(val, 8);
|
||||||
|
QString s = fmtPointer32(val);
|
||||||
|
QString sym = prov.getSymbol((uint64_t)val);
|
||||||
|
if (!sym.isEmpty()) s += QStringLiteral(" // ") + sym;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
case NodeKind::FuncPtr64: {
|
||||||
|
uint64_t val = prov.readU64(addr);
|
||||||
|
if (!display) return rawHex(val, 16);
|
||||||
|
QString s = fmtPointer64(val);
|
||||||
|
QString sym = prov.getSymbol(val);
|
||||||
|
if (!sym.isEmpty()) s += QStringLiteral(" // ") + sym;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
case NodeKind::Vec2:
|
case NodeKind::Vec2:
|
||||||
@@ -259,21 +333,20 @@ static QString readValueImpl(const Node& node, const Provider& prov,
|
|||||||
int count = sizeForKind(node.kind) / 4;
|
int count = sizeForKind(node.kind) / 4;
|
||||||
QStringList parts;
|
QStringList parts;
|
||||||
for (int i = 0; i < count; i++)
|
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(", "));
|
return parts.join(QStringLiteral(", "));
|
||||||
}
|
}
|
||||||
case NodeKind::Mat4x4: {
|
case NodeKind::Mat4x4: {
|
||||||
if (!display) return {}; // not editable as single value
|
if (!display) return {}; // not editable as single value
|
||||||
if (subLine < 0 || subLine >= 4) return QStringLiteral("?");
|
if (subLine < 0 || subLine >= 4) return QStringLiteral("?");
|
||||||
QString line = QStringLiteral("[");
|
QString line = QStringLiteral("row%1 [").arg(subLine);
|
||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
if (c > 0) line += QStringLiteral(", ");
|
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("]");
|
line += QStringLiteral("]");
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
case NodeKind::Padding: return display ? hexVal(prov.readU8(addr)) : rawHex(prov.readU8(addr), 2);
|
|
||||||
case NodeKind::UTF8: {
|
case NodeKind::UTF8: {
|
||||||
QByteArray bytes = prov.readBytes(addr, node.strLen);
|
QByteArray bytes = prov.readBytes(addr, node.strLen);
|
||||||
int end = bytes.indexOf('\0');
|
int end = bytes.indexOf('\0');
|
||||||
@@ -306,48 +379,46 @@ QString readValue(const Node& node, const Provider& prov,
|
|||||||
QString fmtNodeLine(const Node& node, const Provider& prov,
|
QString fmtNodeLine(const Node& node, const Provider& prov,
|
||||||
uint64_t addr, int depth, int subLine,
|
uint64_t addr, int depth, int subLine,
|
||||||
const QString& comment, int colType, int colName,
|
const QString& comment, int colType, int colName,
|
||||||
const QString& typeOverride) {
|
const QString& typeOverride, bool compact) {
|
||||||
QString ind = indent(depth);
|
QString ind = indent(depth);
|
||||||
QString type = typeOverride.isEmpty() ? typeName(node.kind, colType) : fit(typeOverride, colType);
|
|
||||||
QString name = fit(node.name, colName);
|
|
||||||
// Blank prefix for continuation lines (same width as type+sep+name+sep)
|
|
||||||
const int prefixW = colType + colName + 2 * kSepWidth;
|
|
||||||
|
|
||||||
// Comment suffix (padded or empty)
|
// Compute raw type string for overflow detection
|
||||||
QString cmtSuffix = comment.isEmpty() ? QString(COL_COMMENT, ' ')
|
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 = effectiveColType + (overflow ? name.size() : colName) + 2 * kSepWidth;
|
||||||
|
|
||||||
|
// Comment suffix (only present when a comment is provided; no trailing padding)
|
||||||
|
QString cmtSuffix = comment.isEmpty() ? QString()
|
||||||
: fit(comment, COL_COMMENT);
|
: fit(comment, COL_COMMENT);
|
||||||
|
|
||||||
// Mat4x4: subLine 0..3 = rows
|
// Mat4x4: subLine 0..3 = rows — no truncation so large floats always display fully
|
||||||
if (node.kind == NodeKind::Mat4x4) {
|
if (node.kind == NodeKind::Mat4x4) {
|
||||||
QString val = fit(readValue(node, prov, addr, subLine), COL_VALUE);
|
QString val = readValue(node, prov, addr, subLine);
|
||||||
if (subLine == 0) return ind + type + SEP + name + SEP + val + cmtSuffix;
|
if (subLine == 0) return ind + type + SEP + name + SEP + val + cmtSuffix;
|
||||||
return ind + QString(prefixW, ' ') + val + cmtSuffix;
|
return ind + QString(prefixW, ' ') + val + cmtSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hex nodes and Padding: hex byte preview
|
// Hex nodes: hex byte preview (ASCII padded to colName to align with value column)
|
||||||
if (isHexPreview(node.kind)) {
|
if (isHexPreview(node.kind)) {
|
||||||
if (node.kind == NodeKind::Padding) {
|
|
||||||
const int totalSz = qMax(1, node.arrayLen);
|
|
||||||
const int lineOff = subLine * 8;
|
|
||||||
const int lineBytes = qMin(8, totalSz - lineOff);
|
|
||||||
QByteArray b = prov.isReadable(addr + lineOff, lineBytes)
|
|
||||||
? prov.readBytes(addr + lineOff, lineBytes) : QByteArray(lineBytes, '\0');
|
|
||||||
QString ascii = bytesToAscii(b, lineBytes);
|
|
||||||
QString hex = bytesToHex(b, lineBytes).leftJustified(23, ' '); // 8*3-1
|
|
||||||
if (subLine == 0)
|
|
||||||
return ind + type + SEP + ascii + SEP + hex + cmtSuffix;
|
|
||||||
return ind + QString(colType + (int)SEP.size(), ' ') + ascii + SEP + hex + cmtSuffix;
|
|
||||||
}
|
|
||||||
// Hex8..Hex64: single line, ASCII padded to 8 chars so hex column aligns
|
|
||||||
const int sz = sizeForKind(node.kind);
|
const int sz = sizeForKind(node.kind);
|
||||||
QByteArray b = prov.isReadable(addr, sz)
|
QByteArray b = prov.isReadable(addr, sz)
|
||||||
? prov.readBytes(addr, sz) : QByteArray(sz, '\0');
|
? prov.readBytes(addr, sz) : QByteArray(sz, '\0');
|
||||||
QString ascii = bytesToAscii(b, sz).leftJustified(8, ' ');
|
QString ascii = bytesToAscii(b, sz).leftJustified(colName, ' ');
|
||||||
QString hex = bytesToHex(b, sz).leftJustified(23, ' ');
|
QString hex = bytesToHex(b, sz).leftJustified(23, ' ');
|
||||||
return ind + type + SEP + ascii + SEP + hex + cmtSuffix;
|
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;
|
return ind + type + SEP + name + SEP + val + cmtSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,6 +608,14 @@ QByteArray parseValue(NodeKind kind, const QString& text, bool* ok) {
|
|||||||
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
||||||
return *ok ? toBytes<uint64_t>(val) : QByteArray{};
|
return *ok ? toBytes<uint64_t>(val) : QByteArray{};
|
||||||
}
|
}
|
||||||
|
case NodeKind::FuncPtr32: {
|
||||||
|
uint val = stripHex(s).toUInt(ok, 16);
|
||||||
|
return *ok ? toBytes<uint32_t>(val) : QByteArray{};
|
||||||
|
}
|
||||||
|
case NodeKind::FuncPtr64: {
|
||||||
|
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
||||||
|
return *ok ? toBytes<uint64_t>(val) : QByteArray{};
|
||||||
|
}
|
||||||
case NodeKind::UTF8: {
|
case NodeKind::UTF8: {
|
||||||
*ok = true;
|
*ok = true;
|
||||||
if (s.startsWith('"') && s.endsWith('"'))
|
if (s.startsWith('"') && s.endsWith('"'))
|
||||||
@@ -565,7 +644,8 @@ QString validateValue(NodeKind kind, const QString& text) {
|
|||||||
|
|
||||||
// For integer/hex types, validate character set first
|
// For integer/hex types, validate character set first
|
||||||
bool isHexKind = (kind >= NodeKind::Hex8 && kind <= NodeKind::Hex64)
|
bool isHexKind = (kind >= NodeKind::Hex8 && kind <= NodeKind::Hex64)
|
||||||
|| kind == NodeKind::Pointer32 || kind == NodeKind::Pointer64;
|
|| kind == NodeKind::Pointer32 || kind == NodeKind::Pointer64
|
||||||
|
|| kind == NodeKind::FuncPtr32 || kind == NodeKind::FuncPtr64;
|
||||||
bool isIntKind = (kind >= NodeKind::Int8 && kind <= NodeKind::UInt64);
|
bool isIntKind = (kind >= NodeKind::Int8 && kind <= NodeKind::UInt64);
|
||||||
|
|
||||||
if (isHexKind || isIntKind) {
|
if (isHexKind || isIntKind) {
|
||||||
@@ -609,43 +689,41 @@ QString validateValue(NodeKind kind, const QString& text) {
|
|||||||
return QStringLiteral("invalid");
|
return QStringLiteral("invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Base address validation (supports simple +/- equations) ──
|
// ── Base address validation (delegates to AddressParser) ──
|
||||||
|
|
||||||
QString validateBaseAddress(const QString& text) {
|
QString validateBaseAddress(const QString& text) {
|
||||||
QString s = text.trimmed();
|
QString s = text.trimmed();
|
||||||
if (s.isEmpty()) return QStringLiteral("empty");
|
if (s.isEmpty()) return QStringLiteral("empty");
|
||||||
|
//s.remove('`');
|
||||||
|
return AddressParser::validate(s);
|
||||||
|
}
|
||||||
|
|
||||||
int pos = 0;
|
QString fmtEnumMember(const QString& name, int64_t value, int depth, int nameW) {
|
||||||
bool firstTerm = true;
|
QString ind = indent(depth);
|
||||||
|
return ind + name.leftJustified(nameW) + QStringLiteral(" = ") + QString::number(value);
|
||||||
|
}
|
||||||
|
|
||||||
while (pos < s.size()) {
|
// ── Bitfield member formatting ──
|
||||||
// Skip whitespace
|
|
||||||
while (pos < s.size() && s[pos].isSpace()) pos++;
|
|
||||||
if (pos >= s.size()) break;
|
|
||||||
|
|
||||||
// Check for +/- operator (except first term)
|
uint64_t extractBits(const Provider& prov, uint64_t addr,
|
||||||
if (!firstTerm) {
|
NodeKind containerKind,
|
||||||
if (s[pos] == '+' || s[pos] == '-') pos++;
|
uint8_t bitOffset, uint8_t bitWidth) {
|
||||||
else return QStringLiteral("invalid '%1'").arg(s[pos]);
|
uint64_t container = 0;
|
||||||
while (pos < s.size() && s[pos].isSpace()) pos++;
|
switch (containerKind) {
|
||||||
}
|
case NodeKind::Hex8: container = prov.readU8(addr); break;
|
||||||
|
case NodeKind::Hex16: container = prov.readU16(addr); break;
|
||||||
// Skip 0x prefix if present
|
case NodeKind::Hex32: container = prov.readU32(addr); break;
|
||||||
if (pos + 1 < s.size() && s[pos] == '0' && (s[pos+1] == 'x' || s[pos+1] == 'X'))
|
default: container = prov.readU64(addr); break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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
|
} // namespace rcx::fmt
|
||||||
|
|||||||
@@ -44,13 +44,14 @@ static QString cTypeName(NodeKind kind) {
|
|||||||
case NodeKind::Bool: return QStringLiteral("bool");
|
case NodeKind::Bool: return QStringLiteral("bool");
|
||||||
case NodeKind::Pointer32: return QStringLiteral("uint32_t");
|
case NodeKind::Pointer32: return QStringLiteral("uint32_t");
|
||||||
case NodeKind::Pointer64: return QStringLiteral("uint64_t");
|
case NodeKind::Pointer64: return QStringLiteral("uint64_t");
|
||||||
|
case NodeKind::FuncPtr32: return QStringLiteral("uint32_t");
|
||||||
|
case NodeKind::FuncPtr64: return QStringLiteral("uint64_t");
|
||||||
case NodeKind::Vec2: return QStringLiteral("float");
|
case NodeKind::Vec2: return QStringLiteral("float");
|
||||||
case NodeKind::Vec3: return QStringLiteral("float");
|
case NodeKind::Vec3: return QStringLiteral("float");
|
||||||
case NodeKind::Vec4: return QStringLiteral("float");
|
case NodeKind::Vec4: return QStringLiteral("float");
|
||||||
case NodeKind::Mat4x4: return QStringLiteral("float");
|
case NodeKind::Mat4x4: return QStringLiteral("float");
|
||||||
case NodeKind::UTF8: return QStringLiteral("char");
|
case NodeKind::UTF8: return QStringLiteral("char");
|
||||||
case NodeKind::UTF16: return QStringLiteral("wchar_t");
|
case NodeKind::UTF16: return QStringLiteral("wchar_t");
|
||||||
case NodeKind::Padding: return QStringLiteral("uint8_t");
|
|
||||||
default: return QStringLiteral("uint8_t");
|
default: return QStringLiteral("uint8_t");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +68,7 @@ struct GenContext {
|
|||||||
QString output;
|
QString output;
|
||||||
int padCounter = 0;
|
int padCounter = 0;
|
||||||
const QHash<NodeKind, QString>* typeAliases = nullptr;
|
const QHash<NodeKind, QString>* typeAliases = nullptr;
|
||||||
|
bool emitAsserts = false;
|
||||||
|
|
||||||
QString uniquePadName() {
|
QString uniquePadName() {
|
||||||
return QStringLiteral("_pad%1").arg(padCounter++, 4, 16, QChar('0'));
|
return QStringLiteral("_pad%1").arg(padCounter++, 4, 16, QChar('0'));
|
||||||
@@ -93,101 +95,191 @@ struct GenContext {
|
|||||||
// Forward declarations
|
// Forward declarations
|
||||||
static void emitStruct(GenContext& ctx, uint64_t structId);
|
static void emitStruct(GenContext& ctx, uint64_t structId);
|
||||||
|
|
||||||
// ── Emit a single field declaration ──
|
// ── Field line with offset comment (code + marker + comment) ──
|
||||||
|
// We use a \x01 marker to separate the code part from the offset comment.
|
||||||
|
// After all output is generated, alignComments() replaces markers with padding.
|
||||||
|
|
||||||
static QString emitField(GenContext& ctx, const Node& node) {
|
static const QChar kCommentMarker = QChar(0x01);
|
||||||
|
|
||||||
|
static QString offsetComment(int offset, bool isSizeof = false) {
|
||||||
|
if (isSizeof)
|
||||||
|
return QString(kCommentMarker) + QStringLiteral("// sizeof 0x%1").arg(QString::number(offset, 16).toUpper());
|
||||||
|
return QString(kCommentMarker) + QStringLiteral("// 0x%1").arg(QString::number(offset, 16).toUpper());
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString indent(int depth) {
|
||||||
|
return QString(depth * 4, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString emitField(GenContext& ctx, const Node& node, int depth, int baseOffset) {
|
||||||
const NodeTree& tree = ctx.tree;
|
const NodeTree& tree = ctx.tree;
|
||||||
|
QString ind = indent(depth);
|
||||||
QString name = sanitizeIdent(node.name.isEmpty()
|
QString name = sanitizeIdent(node.name.isEmpty()
|
||||||
? QStringLiteral("field_%1").arg(node.offset, 2, 16, QChar('0'))
|
? QStringLiteral("field_%1").arg(node.offset, 2, 16, QChar('0'))
|
||||||
: node.name);
|
: node.name);
|
||||||
|
QString oc = offsetComment(baseOffset + node.offset);
|
||||||
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case NodeKind::Vec2:
|
case NodeKind::Vec2:
|
||||||
return QStringLiteral(" %1 %2[2];").arg(ctx.cType(NodeKind::Float), name);
|
return ind + QStringLiteral("%1 %2[2];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||||
case NodeKind::Vec3:
|
case NodeKind::Vec3:
|
||||||
return QStringLiteral(" %1 %2[3];").arg(ctx.cType(NodeKind::Float), name);
|
return ind + QStringLiteral("%1 %2[3];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||||
case NodeKind::Vec4:
|
case NodeKind::Vec4:
|
||||||
return QStringLiteral(" %1 %2[4];").arg(ctx.cType(NodeKind::Float), name);
|
return ind + QStringLiteral("%1 %2[4];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||||
case NodeKind::Mat4x4:
|
case NodeKind::Mat4x4:
|
||||||
return QStringLiteral(" %1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name);
|
return ind + QStringLiteral("%1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||||
case NodeKind::UTF8:
|
case NodeKind::UTF8:
|
||||||
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen);
|
return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen) + oc;
|
||||||
case NodeKind::UTF16:
|
case NodeKind::UTF16:
|
||||||
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen);
|
return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen) + oc;
|
||||||
case NodeKind::Padding:
|
case NodeKind::Pointer32:
|
||||||
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::Padding), name).arg(qMax(1, node.arrayLen));
|
|
||||||
case NodeKind::Pointer32: {
|
|
||||||
if (node.refId != 0) {
|
|
||||||
int refIdx = tree.indexOfId(node.refId);
|
|
||||||
if (refIdx >= 0) {
|
|
||||||
QString target = ctx.structName(tree.nodes[refIdx]);
|
|
||||||
return QStringLiteral(" %1 %2; // -> %3*").arg(ctx.cType(NodeKind::Pointer32), name, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QStringLiteral(" %1 %2;").arg(ctx.cType(NodeKind::Pointer32), name);
|
|
||||||
}
|
|
||||||
case NodeKind::Pointer64: {
|
case NodeKind::Pointer64: {
|
||||||
if (node.refId != 0) {
|
if (node.refId != 0) {
|
||||||
int refIdx = tree.indexOfId(node.refId);
|
int refIdx = tree.indexOfId(node.refId);
|
||||||
if (refIdx >= 0) {
|
if (refIdx >= 0) {
|
||||||
QString target = ctx.structName(tree.nodes[refIdx]);
|
QString target = ctx.structName(tree.nodes[refIdx]);
|
||||||
return QStringLiteral(" %1* %2;").arg(target, name);
|
return ind + QStringLiteral("struct %1* %2;").arg(target, name) + oc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QStringLiteral(" void* %1;").arg(name);
|
// Native pointer: use void* when this is the target's natural pointer kind
|
||||||
|
bool isNativePtr = (node.kind == NodeKind::Pointer32 && ctx.tree.pointerSize <= 4)
|
||||||
|
|| (node.kind == NodeKind::Pointer64 && ctx.tree.pointerSize >= 8);
|
||||||
|
if (isNativePtr)
|
||||||
|
return ind + QStringLiteral("void* %1;").arg(name) + oc;
|
||||||
|
// Cross-size pointer: fall back to raw integer type
|
||||||
|
return ind + QStringLiteral("%1 %2;").arg(ctx.cType(node.kind), name) + oc;
|
||||||
}
|
}
|
||||||
|
case NodeKind::FuncPtr32:
|
||||||
|
return ind + QStringLiteral("void (*%1)();").arg(name) + oc;
|
||||||
|
case NodeKind::FuncPtr64:
|
||||||
|
return ind + QStringLiteral("void (*%1)();").arg(name) + oc;
|
||||||
default:
|
default:
|
||||||
return QStringLiteral(" %1 %2;").arg(ctx.cType(node.kind), name);
|
return ind + QStringLiteral("%1 %2;").arg(ctx.cType(node.kind), name) + oc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Emit struct body (fields + padding) ──
|
// ── Emit struct body (fields + padding) — Vergilius-style ──
|
||||||
|
|
||||||
static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
static void emitStructBody(GenContext& ctx, uint64_t structId,
|
||||||
|
bool isUnion, int depth, int baseOffset) {
|
||||||
const NodeTree& tree = ctx.tree;
|
const NodeTree& tree = ctx.tree;
|
||||||
int idx = tree.indexOfId(structId);
|
int idx = tree.indexOfId(structId);
|
||||||
if (idx < 0) return;
|
if (idx < 0) return;
|
||||||
|
|
||||||
int structSize = tree.structSpan(structId, &ctx.childMap);
|
int structSize = tree.structSpan(structId, &ctx.childMap);
|
||||||
|
QString ind = indent(depth);
|
||||||
|
|
||||||
QVector<int> children = ctx.childMap.value(structId);
|
QVector<int> allChildren = ctx.childMap.value(structId);
|
||||||
|
QVector<int> children, staticIdxs;
|
||||||
|
for (int ci : allChildren) {
|
||||||
|
if (tree.nodes[ci].isStatic)
|
||||||
|
staticIdxs.append(ci);
|
||||||
|
else
|
||||||
|
children.append(ci);
|
||||||
|
}
|
||||||
std::sort(children.begin(), children.end(), [&](int a, int b) {
|
std::sort(children.begin(), children.end(), [&](int a, int b) {
|
||||||
return tree.nodes[a].offset < tree.nodes[b].offset;
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||||
});
|
});
|
||||||
|
|
||||||
int cursor = 0;
|
// Helper: emit a padding/hex run as a single collapsed byte array
|
||||||
|
auto emitPadRun = [&](int relOffset, int size) {
|
||||||
|
if (size <= 0) return;
|
||||||
|
ctx.output += ind + QStringLiteral("uint8_t %1[0x%2];%3\n")
|
||||||
|
.arg(ctx.uniquePadName())
|
||||||
|
.arg(QString::number(size, 16).toUpper())
|
||||||
|
.arg(offsetComment(baseOffset + relOffset));
|
||||||
|
};
|
||||||
|
|
||||||
for (int ci : children) {
|
int cursor = 0;
|
||||||
const Node& child = tree.nodes[ci];
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < children.size()) {
|
||||||
|
const Node& child = tree.nodes[children[i]];
|
||||||
int childSize;
|
int childSize;
|
||||||
if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
||||||
childSize = tree.structSpan(child.id, &ctx.childMap);
|
childSize = tree.structSpan(child.id, &ctx.childMap);
|
||||||
else
|
else
|
||||||
childSize = child.byteSize();
|
childSize = child.byteSize();
|
||||||
|
|
||||||
// Gap before this field
|
// Gap/overlap handling (skip for unions)
|
||||||
if (child.offset > cursor) {
|
if (!isUnion) {
|
||||||
int gap = child.offset - cursor;
|
if (child.offset > cursor)
|
||||||
ctx.output += QStringLiteral(" %1 %2[0x%3];\n")
|
emitPadRun(cursor, child.offset - cursor);
|
||||||
.arg(ctx.cType(NodeKind::Padding))
|
else if (child.offset < cursor)
|
||||||
.arg(ctx.uniquePadName())
|
ctx.output += ind + QStringLiteral("// WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n")
|
||||||
.arg(QString::number(gap, 16).toUpper());
|
.arg(QString::number(baseOffset + child.offset, 16).toUpper())
|
||||||
} else if (child.offset < cursor) {
|
.arg(QString::number(baseOffset + cursor, 16).toUpper());
|
||||||
// Overlap
|
}
|
||||||
ctx.output += QStringLiteral(" // WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n")
|
|
||||||
.arg(QString::number(child.offset, 16).toUpper())
|
// Collapse consecutive hex nodes into a single padding array
|
||||||
.arg(QString::number(cursor, 16).toUpper());
|
if (isHexNode(child.kind)) {
|
||||||
|
int runStart = child.offset;
|
||||||
|
int runEnd = child.offset + childSize;
|
||||||
|
int j = i + 1;
|
||||||
|
while (j < children.size()) {
|
||||||
|
const Node& next = tree.nodes[children[j]];
|
||||||
|
if (!isHexNode(next.kind)) break;
|
||||||
|
int nextSize = next.byteSize();
|
||||||
|
if (next.offset < runEnd) break;
|
||||||
|
runEnd = next.offset + nextSize;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
emitPadRun(runStart, runEnd - runStart);
|
||||||
|
cursor = runEnd;
|
||||||
|
i = j;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit the field
|
// Emit the field
|
||||||
if (child.kind == NodeKind::Struct) {
|
if (child.kind == NodeKind::Struct) {
|
||||||
// Ensure the nested struct type is emitted first
|
// Bitfield container — emit inline bitfield members
|
||||||
emitStruct(ctx, child.id);
|
if (child.classKeyword == QStringLiteral("bitfield")
|
||||||
QString typeName = ctx.structName(child);
|
&& !child.bitfieldMembers.isEmpty()) {
|
||||||
QString fieldName = sanitizeIdent(child.name);
|
QString bfType = ctx.cType(child.elementKind);
|
||||||
ctx.output += QStringLiteral(" %1 %2;\n").arg(typeName, fieldName);
|
if (bfType.isEmpty()) bfType = QStringLiteral("uint32_t");
|
||||||
|
QString fieldName = child.name.isEmpty()
|
||||||
|
? QString() : QStringLiteral(" ") + sanitizeIdent(child.name);
|
||||||
|
ctx.output += ind + QStringLiteral("struct\n");
|
||||||
|
ctx.output += ind + QStringLiteral("{\n");
|
||||||
|
QString bfInd = indent(depth + 1);
|
||||||
|
for (const auto& m : child.bitfieldMembers) {
|
||||||
|
ctx.output += bfInd + bfType + QStringLiteral(" ")
|
||||||
|
+ sanitizeIdent(m.name) + QStringLiteral(" : ")
|
||||||
|
+ QString::number(m.bitWidth) + QStringLiteral(";")
|
||||||
|
+ offsetComment(baseOffset + child.offset)
|
||||||
|
+ QStringLiteral("\n");
|
||||||
|
}
|
||||||
|
ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";")
|
||||||
|
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bool isAnonymous = child.structTypeName.isEmpty();
|
||||||
|
|
||||||
|
if (isAnonymous) {
|
||||||
|
// Inline anonymous struct/union
|
||||||
|
QString kw = child.resolvedClassKeyword();
|
||||||
|
ctx.output += ind + kw + QStringLiteral("\n");
|
||||||
|
ctx.output += ind + QStringLiteral("{\n");
|
||||||
|
bool childIsUnion = (kw == QStringLiteral("union"));
|
||||||
|
emitStructBody(ctx, child.id, childIsUnion, depth + 1,
|
||||||
|
baseOffset + child.offset);
|
||||||
|
QString fieldName = child.name.isEmpty()
|
||||||
|
? QString() : QStringLiteral(" ") + sanitizeIdent(child.name);
|
||||||
|
ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";")
|
||||||
|
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
|
||||||
|
} else {
|
||||||
|
// Named struct — reference by name with struct keyword prefix
|
||||||
|
QString kw = child.resolvedClassKeyword();
|
||||||
|
if (kw == QStringLiteral("enum") && child.enumMembers.isEmpty())
|
||||||
|
kw = QStringLiteral("struct");
|
||||||
|
QString typeName = sanitizeIdent(child.structTypeName);
|
||||||
|
QString fieldName = sanitizeIdent(child.name);
|
||||||
|
ctx.output += ind + kw + QStringLiteral(" ") + typeName
|
||||||
|
+ QStringLiteral(" ") + fieldName + QStringLiteral(";")
|
||||||
|
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
|
||||||
|
}
|
||||||
|
} // end bitfield else
|
||||||
} else if (child.kind == NodeKind::Array) {
|
} else if (child.kind == NodeKind::Array) {
|
||||||
// Check if array has struct element children
|
|
||||||
QVector<int> arrayKids = ctx.childMap.value(child.id);
|
QVector<int> arrayKids = ctx.childMap.value(child.id);
|
||||||
bool hasStructChild = false;
|
bool hasStructChild = false;
|
||||||
QString elemTypeName;
|
QString elemTypeName;
|
||||||
@@ -195,7 +287,6 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
|||||||
for (int ak : arrayKids) {
|
for (int ak : arrayKids) {
|
||||||
if (tree.nodes[ak].kind == NodeKind::Struct) {
|
if (tree.nodes[ak].kind == NodeKind::Struct) {
|
||||||
hasStructChild = true;
|
hasStructChild = true;
|
||||||
emitStruct(ctx, tree.nodes[ak].id);
|
|
||||||
elemTypeName = ctx.structName(tree.nodes[ak]);
|
elemTypeName = ctx.structName(tree.nodes[ak]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -203,31 +294,37 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
|||||||
|
|
||||||
QString fieldName = sanitizeIdent(child.name);
|
QString fieldName = sanitizeIdent(child.name);
|
||||||
if (hasStructChild && !elemTypeName.isEmpty()) {
|
if (hasStructChild && !elemTypeName.isEmpty()) {
|
||||||
ctx.output += QStringLiteral(" %1 %2[%3];\n")
|
ctx.output += ind + QStringLiteral("struct %1 %2[%3];%4\n")
|
||||||
.arg(elemTypeName, fieldName).arg(child.arrayLen);
|
.arg(elemTypeName, fieldName).arg(child.arrayLen)
|
||||||
|
.arg(offsetComment(baseOffset + child.offset));
|
||||||
} else {
|
} else {
|
||||||
ctx.output += QStringLiteral(" %1 %2[%3];\n")
|
ctx.output += ind + QStringLiteral("%1 %2[%3];%4\n")
|
||||||
.arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen);
|
.arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen)
|
||||||
|
.arg(offsetComment(baseOffset + child.offset));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.output += emitField(ctx, child) + QStringLiteral("\n");
|
ctx.output += emitField(ctx, child, depth, baseOffset) + QStringLiteral("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int childEnd = child.offset + childSize;
|
int childEnd = child.offset + childSize;
|
||||||
if (childEnd > cursor) cursor = childEnd;
|
if (childEnd > cursor) cursor = childEnd;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tail padding
|
// Tail padding (skip for unions)
|
||||||
if (cursor < structSize) {
|
if (!isUnion && cursor < structSize)
|
||||||
int gap = structSize - cursor;
|
emitPadRun(cursor, structSize - cursor);
|
||||||
ctx.output += QStringLiteral(" %1 %2[0x%3];\n")
|
|
||||||
.arg(ctx.cType(NodeKind::Padding))
|
// Emit static field comments (static fields are runtime-only, not part of struct layout)
|
||||||
.arg(ctx.uniquePadName())
|
for (int si : staticIdxs) {
|
||||||
.arg(QString::number(gap, 16).toUpper());
|
const Node& sf = tree.nodes[si];
|
||||||
|
QString sfType = sf.structTypeName.isEmpty() ? ctx.cType(sf.kind) : sf.structTypeName;
|
||||||
|
ctx.output += ind + QStringLiteral("// static: %1 %2 @ %3\n")
|
||||||
|
.arg(sfType, sanitizeIdent(sf.name), sf.offsetExpr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Emit a complete struct definition ──
|
// ── Emit a complete top-level struct definition (Vergilius-style) ──
|
||||||
|
|
||||||
static void emitStruct(GenContext& ctx, uint64_t structId) {
|
static void emitStruct(GenContext& ctx, uint64_t structId) {
|
||||||
if (ctx.emittedIds.contains(structId)) return;
|
if (ctx.emittedIds.contains(structId)) return;
|
||||||
@@ -243,19 +340,12 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For arrays, we don't emit a top-level struct — the array itself
|
|
||||||
// is a field inside its parent. But we do emit struct element types.
|
|
||||||
if (node.kind == NodeKind::Array) {
|
if (node.kind == NodeKind::Array) {
|
||||||
QVector<int> kids = ctx.childMap.value(structId);
|
|
||||||
for (int ki : kids) {
|
|
||||||
if (ctx.tree.nodes[ki].kind == NodeKind::Struct)
|
|
||||||
emitStruct(ctx, ctx.tree.nodes[ki].id);
|
|
||||||
}
|
|
||||||
ctx.visiting.remove(structId);
|
ctx.visiting.remove(structId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate by struct type name (different nodes may share the same type)
|
// Deduplicate by struct type name
|
||||||
QString typeName = ctx.structName(node);
|
QString typeName = ctx.structName(node);
|
||||||
if (ctx.emittedTypeNames.contains(typeName)) {
|
if (ctx.emittedTypeNames.contains(typeName)) {
|
||||||
ctx.emittedIds.insert(structId);
|
ctx.emittedIds.insert(structId);
|
||||||
@@ -263,49 +353,39 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit nested struct types first (dependency order)
|
|
||||||
QVector<int> children = ctx.childMap.value(structId);
|
|
||||||
for (int ci : children) {
|
|
||||||
const Node& child = ctx.tree.nodes[ci];
|
|
||||||
if (child.kind == NodeKind::Struct)
|
|
||||||
emitStruct(ctx, child.id);
|
|
||||||
else if (child.kind == NodeKind::Array) {
|
|
||||||
QVector<int> arrayKids = ctx.childMap.value(child.id);
|
|
||||||
for (int ak : arrayKids) {
|
|
||||||
if (ctx.tree.nodes[ak].kind == NodeKind::Struct)
|
|
||||||
emitStruct(ctx, ctx.tree.nodes[ak].id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Forward-declare pointer target types if they're outside this subtree
|
|
||||||
if (child.kind == NodeKind::Pointer64 && child.refId != 0) {
|
|
||||||
int refIdx = ctx.tree.indexOfId(child.refId);
|
|
||||||
if (refIdx >= 0 && !ctx.emittedIds.contains(child.refId)
|
|
||||||
&& !ctx.forwardDeclared.contains(child.refId)) {
|
|
||||||
QString fwdName = ctx.structName(ctx.tree.nodes[refIdx]);
|
|
||||||
QString fwdKw = ctx.tree.nodes[refIdx].resolvedClassKeyword();
|
|
||||||
if (fwdKw == QStringLiteral("enum")) fwdKw = QStringLiteral("struct");
|
|
||||||
ctx.output += QStringLiteral("%1 %2;\n").arg(fwdKw, fwdName);
|
|
||||||
ctx.forwardDeclared.insert(child.refId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.emittedIds.insert(structId);
|
ctx.emittedIds.insert(structId);
|
||||||
ctx.emittedTypeNames.insert(typeName);
|
ctx.emittedTypeNames.insert(typeName);
|
||||||
int structSize = ctx.tree.structSpan(structId, &ctx.childMap);
|
int structSize = ctx.tree.structSpan(structId, &ctx.childMap);
|
||||||
|
|
||||||
ctx.output += QStringLiteral("#pragma pack(push, 1)\n");
|
|
||||||
QString kw = node.resolvedClassKeyword();
|
QString kw = node.resolvedClassKeyword();
|
||||||
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct"); // enum is cosmetic
|
|
||||||
ctx.output += QStringLiteral("%1 %2 {\n").arg(kw, typeName);
|
|
||||||
|
|
||||||
emitStructBody(ctx, structId);
|
// Enum with members: emit as proper C enum
|
||||||
|
if (kw == QStringLiteral("enum") && !node.enumMembers.isEmpty()) {
|
||||||
|
ctx.output += QStringLiteral("enum %1 {\n").arg(typeName);
|
||||||
|
for (const auto& m : node.enumMembers) {
|
||||||
|
ctx.output += QStringLiteral(" %1 = %2,\n")
|
||||||
|
.arg(sanitizeIdent(m.first))
|
||||||
|
.arg(m.second);
|
||||||
|
}
|
||||||
|
ctx.output += QStringLiteral("};\n\n");
|
||||||
|
ctx.visiting.remove(structId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.output += QStringLiteral("};\n");
|
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct");
|
||||||
ctx.output += QStringLiteral("#pragma pack(pop)\n");
|
|
||||||
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n\n")
|
ctx.output += kw + QStringLiteral(" ") + typeName + QStringLiteral("\n{\n");
|
||||||
.arg(typeName)
|
|
||||||
.arg(QString::number(structSize, 16).toUpper());
|
emitStructBody(ctx, structId, kw == QStringLiteral("union"), 1, 0);
|
||||||
|
|
||||||
|
ctx.output += QStringLiteral("};")
|
||||||
|
+ offsetComment(structSize, true)
|
||||||
|
+ QStringLiteral("\n");
|
||||||
|
if (ctx.emitAsserts)
|
||||||
|
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n")
|
||||||
|
.arg(typeName)
|
||||||
|
.arg(QString::number(structSize, 16).toUpper());
|
||||||
|
ctx.output += QStringLiteral("\n");
|
||||||
|
|
||||||
ctx.visiting.remove(structId);
|
ctx.visiting.remove(structId);
|
||||||
}
|
}
|
||||||
@@ -319,22 +399,39 @@ static QHash<uint64_t, QVector<int>> buildChildMap(const NodeTree& tree) {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Path breadcrumb for header comment ──
|
// ── Align offset comments ──
|
||||||
|
// Replaces kCommentMarker with spaces so all "// 0x..." comments align to
|
||||||
|
// the same column (the longest code portion + 1 space).
|
||||||
|
|
||||||
static QString nodePath(const NodeTree& tree, uint64_t nodeId) {
|
static QString alignComments(const QString& raw) {
|
||||||
QStringList parts;
|
QStringList lines = raw.split('\n');
|
||||||
QSet<uint64_t> seen;
|
|
||||||
uint64_t cur = nodeId;
|
// First pass: find the maximum code width (text before the marker)
|
||||||
while (cur != 0 && !seen.contains(cur)) {
|
int maxCode = 0;
|
||||||
seen.insert(cur);
|
for (const QString& line : lines) {
|
||||||
int idx = tree.indexOfId(cur);
|
int pos = line.indexOf(kCommentMarker);
|
||||||
if (idx < 0) break;
|
if (pos >= 0)
|
||||||
const Node& n = tree.nodes[idx];
|
maxCode = qMax(maxCode, pos);
|
||||||
parts << (n.name.isEmpty() ? QStringLiteral("<unnamed>") : n.name);
|
|
||||||
cur = n.parentId;
|
|
||||||
}
|
}
|
||||||
std::reverse(parts.begin(), parts.end());
|
|
||||||
return parts.join(QStringLiteral(" > "));
|
// Second pass: replace markers with padding
|
||||||
|
QString result;
|
||||||
|
result.reserve(raw.size() + lines.size() * 8);
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
if (i > 0) result += '\n';
|
||||||
|
const QString& line = lines[i];
|
||||||
|
int pos = line.indexOf(kCommentMarker);
|
||||||
|
if (pos >= 0) {
|
||||||
|
result += line.left(pos);
|
||||||
|
int pad = maxCode - pos + 1;
|
||||||
|
if (pad < 1) pad = 1;
|
||||||
|
result += QString(pad, ' ');
|
||||||
|
result += line.mid(pos + 1); // skip the marker char
|
||||||
|
} else {
|
||||||
|
result += line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
@@ -342,38 +439,29 @@ static QString nodePath(const NodeTree& tree, uint64_t nodeId) {
|
|||||||
// ── Public API ──
|
// ── Public API ──
|
||||||
|
|
||||||
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||||
const QHash<NodeKind, QString>* typeAliases) {
|
const QHash<NodeKind, QString>* typeAliases,
|
||||||
|
bool emitAsserts) {
|
||||||
int idx = tree.indexOfId(rootStructId);
|
int idx = tree.indexOfId(rootStructId);
|
||||||
if (idx < 0) return {};
|
if (idx < 0) return {};
|
||||||
|
|
||||||
const Node& root = tree.nodes[idx];
|
const Node& root = tree.nodes[idx];
|
||||||
if (root.kind != NodeKind::Struct) return {};
|
if (root.kind != NodeKind::Struct) return {};
|
||||||
|
|
||||||
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases};
|
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
||||||
int rootSize = tree.structSpan(rootStructId, &ctx.childMap);
|
|
||||||
QString typeName = ctx.structName(root);
|
|
||||||
|
|
||||||
ctx.output += QStringLiteral("// Generated by ReclassX\n");
|
ctx.output += QStringLiteral("#pragma once\n\n");
|
||||||
ctx.output += QStringLiteral("// Rendered from: %1 (id=0x%2, size=0x%3)\n\n")
|
|
||||||
.arg(nodePath(tree, rootStructId))
|
|
||||||
.arg(QString::number(rootStructId, 16).toUpper())
|
|
||||||
.arg(QString::number(rootSize, 16).toUpper());
|
|
||||||
ctx.output += QStringLiteral("#pragma once\n");
|
|
||||||
ctx.output += QStringLiteral("#include <cstdint>\n\n");
|
|
||||||
|
|
||||||
emitStruct(ctx, rootStructId);
|
emitStruct(ctx, rootStructId);
|
||||||
|
|
||||||
return ctx.output;
|
return alignComments(ctx.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString renderCppAll(const NodeTree& tree,
|
QString renderCppAll(const NodeTree& tree,
|
||||||
const QHash<NodeKind, QString>* typeAliases) {
|
const QHash<NodeKind, QString>* typeAliases,
|
||||||
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases};
|
bool emitAsserts) {
|
||||||
|
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
||||||
|
|
||||||
ctx.output += QStringLiteral("// Generated by ReclassX\n");
|
ctx.output += QStringLiteral("#pragma once\n\n");
|
||||||
ctx.output += QStringLiteral("// Full SDK export\n\n");
|
|
||||||
ctx.output += QStringLiteral("#pragma once\n");
|
|
||||||
ctx.output += QStringLiteral("#include <cstdint>\n\n");
|
|
||||||
|
|
||||||
QVector<int> roots = ctx.childMap.value(0);
|
QVector<int> roots = ctx.childMap.value(0);
|
||||||
std::sort(roots.begin(), roots.end(), [&](int a, int b) {
|
std::sort(roots.begin(), roots.end(), [&](int a, int b) {
|
||||||
@@ -385,7 +473,7 @@ QString renderCppAll(const NodeTree& tree,
|
|||||||
emitStruct(ctx, tree.nodes[ri].id);
|
emitStruct(ctx, tree.nodes[ri].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.output;
|
return alignComments(ctx.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString renderNull(const NodeTree&, uint64_t) {
|
QString renderNull(const NodeTree&, uint64_t) {
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ namespace rcx {
|
|||||||
// Generate C++ struct definitions for a single root struct and all
|
// Generate C++ struct definitions for a single root struct and all
|
||||||
// nested/referenced types reachable from it.
|
// nested/referenced types reachable from it.
|
||||||
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||||
const QHash<NodeKind, QString>* typeAliases = nullptr);
|
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||||
|
bool emitAsserts = false);
|
||||||
|
|
||||||
// Generate C++ struct definitions for every root-level struct (full SDK).
|
// Generate C++ struct definitions for every root-level struct (full SDK).
|
||||||
QString renderCppAll(const NodeTree& tree,
|
QString renderCppAll(const NodeTree& tree,
|
||||||
const QHash<NodeKind, QString>* typeAliases = nullptr);
|
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||||
|
bool emitAsserts = false);
|
||||||
|
|
||||||
// Null generator placeholder (returns empty string).
|
// Null generator placeholder (returns empty string).
|
||||||
QString renderNull(const NodeTree& tree, uint64_t rootStructId);
|
QString renderNull(const NodeTree& tree, uint64_t rootStructId);
|
||||||
|
|||||||
BIN
src/icons/class.icns
Normal file
BIN
src/icons/class.ico
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
src/icons/class.png
Normal file
|
After Width: | Height: | Size: 918 B |
222
src/imports/export_reclass_xml.cpp
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#include "export_reclass_xml.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QXmlStreamWriter>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QVector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
// Reverse type map: NodeKind -> ReClassEx V2016 XML Type integer
|
||||||
|
static int xmlTypeForKind(NodeKind kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case NodeKind::Struct: return 1; // ClassInstance
|
||||||
|
case NodeKind::Hex32: return 4;
|
||||||
|
case NodeKind::Hex64: return 5;
|
||||||
|
case NodeKind::Hex16: return 6;
|
||||||
|
case NodeKind::Hex8: return 7;
|
||||||
|
case NodeKind::Pointer64: return 8; // ClassPointer
|
||||||
|
case NodeKind::Pointer32: return 8;
|
||||||
|
case NodeKind::Int64: return 9;
|
||||||
|
case NodeKind::Int32: return 10;
|
||||||
|
case NodeKind::Int16: return 11;
|
||||||
|
case NodeKind::Int8: return 12;
|
||||||
|
case NodeKind::Float: return 13;
|
||||||
|
case NodeKind::Double: return 14;
|
||||||
|
case NodeKind::UInt32: return 15;
|
||||||
|
case NodeKind::UInt16: return 16;
|
||||||
|
case NodeKind::UInt8: return 17;
|
||||||
|
case NodeKind::UInt64: return 32;
|
||||||
|
case NodeKind::UTF8: return 18;
|
||||||
|
case NodeKind::UTF16: return 19;
|
||||||
|
case NodeKind::Bool: return 17; // No native bool in ReClass, map to UInt8
|
||||||
|
case NodeKind::Vec2: return 22;
|
||||||
|
case NodeKind::Vec3: return 23;
|
||||||
|
case NodeKind::Vec4: return 24;
|
||||||
|
case NodeKind::Mat4x4: return 25;
|
||||||
|
case NodeKind::Array: return 27; // ClassInstanceArray
|
||||||
|
}
|
||||||
|
return 7; // fallback to Hex8
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nodeSizeForExport(const Node& node) {
|
||||||
|
switch (node.kind) {
|
||||||
|
case NodeKind::UTF8: return node.strLen;
|
||||||
|
case NodeKind::UTF16: return node.strLen * 2;
|
||||||
|
case NodeKind::Array: {
|
||||||
|
int elemSz = sizeForKind(node.elementKind);
|
||||||
|
return node.arrayLen * (elemSz > 0 ? elemSz : 0);
|
||||||
|
}
|
||||||
|
default: return sizeForKind(node.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve a struct type name from a node ID
|
||||||
|
static QString resolveStructName(const NodeTree& tree, uint64_t refId) {
|
||||||
|
int idx = tree.indexOfId(refId);
|
||||||
|
if (idx < 0) return {};
|
||||||
|
const Node& ref = tree.nodes[idx];
|
||||||
|
if (!ref.structTypeName.isEmpty()) return ref.structTypeName;
|
||||||
|
return ref.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exportReclassXml(const NodeTree& tree, const QString& filePath, QString* errorMsg) {
|
||||||
|
if (tree.nodes.isEmpty()) {
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("No nodes to export");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("Cannot open file for writing: ") + filePath;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build child map
|
||||||
|
QHash<uint64_t, QVector<int>> childMap;
|
||||||
|
for (int i = 0; i < tree.nodes.size(); i++)
|
||||||
|
childMap[tree.nodes[i].parentId].append(i);
|
||||||
|
|
||||||
|
QXmlStreamWriter xml(&file);
|
||||||
|
xml.setAutoFormatting(true);
|
||||||
|
xml.setAutoFormattingIndent(4);
|
||||||
|
xml.writeStartDocument();
|
||||||
|
|
||||||
|
xml.writeStartElement(QStringLiteral("ReClass"));
|
||||||
|
xml.writeComment(QStringLiteral("ReClassEx"));
|
||||||
|
|
||||||
|
// Get root structs
|
||||||
|
QVector<int> roots = childMap.value(0);
|
||||||
|
std::sort(roots.begin(), roots.end(), [&](int a, int b) {
|
||||||
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||||
|
});
|
||||||
|
|
||||||
|
int classCount = 0;
|
||||||
|
|
||||||
|
for (int ri : roots) {
|
||||||
|
const Node& root = tree.nodes[ri];
|
||||||
|
if (root.kind != NodeKind::Struct) continue;
|
||||||
|
|
||||||
|
xml.writeStartElement(QStringLiteral("Class"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Name"), root.name.isEmpty() ? root.structTypeName : root.name);
|
||||||
|
xml.writeAttribute(QStringLiteral("Type"), QStringLiteral("28"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Comment"), QString());
|
||||||
|
xml.writeAttribute(QStringLiteral("Offset"), QStringLiteral("0"));
|
||||||
|
xml.writeAttribute(QStringLiteral("strOffset"), QStringLiteral("0"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Code"), QString());
|
||||||
|
|
||||||
|
// Get children sorted by offset
|
||||||
|
QVector<int> children = childMap.value(root.id);
|
||||||
|
std::sort(children.begin(), children.end(), [&](int a, int b) {
|
||||||
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||||
|
});
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
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;
|
||||||
|
int runEnd = child.offset + child.byteSize();
|
||||||
|
int j = i + 1;
|
||||||
|
while (j < children.size()) {
|
||||||
|
const Node& next = tree.nodes[children[j]];
|
||||||
|
if (!isHexNode(next.kind)) break;
|
||||||
|
if (next.offset < runEnd) break; // overlap
|
||||||
|
runEnd = next.offset + next.byteSize();
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
int totalSize = runEnd - runStart;
|
||||||
|
xml.writeStartElement(QStringLiteral("Node"));
|
||||||
|
// Use first hex node's name if it's a single node, otherwise generate
|
||||||
|
QString hexName = (j - i == 1 && !child.name.isEmpty()) ? child.name : QString();
|
||||||
|
xml.writeAttribute(QStringLiteral("Name"), hexName);
|
||||||
|
xml.writeAttribute(QStringLiteral("Type"), QStringLiteral("21")); // Custom
|
||||||
|
xml.writeAttribute(QStringLiteral("Size"), QString::number(totalSize));
|
||||||
|
xml.writeAttribute(QStringLiteral("bHidden"), QStringLiteral("false"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Comment"), QString());
|
||||||
|
xml.writeEndElement(); // Node
|
||||||
|
i = j;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.writeStartElement(QStringLiteral("Node"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Name"), child.name);
|
||||||
|
xml.writeAttribute(QStringLiteral("Type"), QString::number(xmlTypeForKind(child.kind)));
|
||||||
|
xml.writeAttribute(QStringLiteral("Size"), QString::number(nodeSizeForExport(child)));
|
||||||
|
xml.writeAttribute(QStringLiteral("bHidden"), QStringLiteral("false"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Comment"), QString());
|
||||||
|
|
||||||
|
// Pointer with target
|
||||||
|
if ((child.kind == NodeKind::Pointer64 || child.kind == NodeKind::Pointer32) && child.refId != 0) {
|
||||||
|
QString target = resolveStructName(tree, child.refId);
|
||||||
|
if (!target.isEmpty())
|
||||||
|
xml.writeAttribute(QStringLiteral("Pointer"), target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embedded struct instance
|
||||||
|
if (child.kind == NodeKind::Struct) {
|
||||||
|
QString instName = child.structTypeName.isEmpty() ? child.name : child.structTypeName;
|
||||||
|
xml.writeAttribute(QStringLiteral("Instance"), instName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array: Total attribute and child <Array> element
|
||||||
|
if (child.kind == NodeKind::Array) {
|
||||||
|
xml.writeAttribute(QStringLiteral("Total"), QString::number(child.arrayLen));
|
||||||
|
|
||||||
|
// Resolve element type name
|
||||||
|
QString elemName;
|
||||||
|
if (child.elementKind == NodeKind::Struct && !child.structTypeName.isEmpty()) {
|
||||||
|
elemName = child.structTypeName;
|
||||||
|
} else if (child.refId != 0) {
|
||||||
|
elemName = resolveStructName(tree, child.refId);
|
||||||
|
}
|
||||||
|
if (elemName.isEmpty())
|
||||||
|
elemName = kindToString(child.elementKind);
|
||||||
|
|
||||||
|
xml.writeStartElement(QStringLiteral("Array"));
|
||||||
|
xml.writeAttribute(QStringLiteral("Name"), elemName);
|
||||||
|
xml.writeAttribute(QStringLiteral("Total"), QString::number(child.arrayLen));
|
||||||
|
xml.writeEndElement(); // Array
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.writeEndElement(); // Node
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.writeEndElement(); // Class
|
||||||
|
classCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.writeEndElement(); // ReClass
|
||||||
|
xml.writeEndDocument();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (classCount == 0) {
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("No struct classes found to export");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
10
src/imports/export_reclass_xml.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
// Export a NodeTree to ReClass .NET / ReClassEx compatible XML format.
|
||||||
|
// Returns true on success; populates errorMsg on failure if non-null.
|
||||||
|
bool exportReclassXml(const NodeTree& tree, const QString& filePath, QString* errorMsg = nullptr);
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
1147
src/imports/import_pdb.cpp
Normal file
35
src/imports/import_pdb.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
#include <QVector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
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
|
||||||
184
src/imports/import_pdb_dialog.cpp
Normal 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
|
||||||
53
src/imports/import_pdb_dialog.h
Normal 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
|
||||||
392
src/imports/import_reclass_xml.cpp
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
#include "import_reclass_xml.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
// ── Version-specific type maps ──
|
||||||
|
// Maps XML Type attribute (integer) → NodeKind.
|
||||||
|
// Entries with no rcx equivalent use Hex8 as fallback.
|
||||||
|
|
||||||
|
enum class XmlVersion { V2013, V2016 };
|
||||||
|
|
||||||
|
// 2016 / ReClassEx / MemeClsEx type map (35 entries, index = XML Type value)
|
||||||
|
static const struct { int xmlType; NodeKind kind; } kTypeMap2016[] = {
|
||||||
|
// 0: null (unused)
|
||||||
|
{ 1, NodeKind::Struct }, // ClassInstance
|
||||||
|
// 2,3: null
|
||||||
|
{ 4, NodeKind::Hex32 },
|
||||||
|
{ 5, NodeKind::Hex64 },
|
||||||
|
{ 6, NodeKind::Hex16 },
|
||||||
|
{ 7, NodeKind::Hex8 },
|
||||||
|
{ 8, NodeKind::Pointer64 }, // ClassPointer
|
||||||
|
{ 9, NodeKind::Int64 },
|
||||||
|
{ 10, NodeKind::Int32 },
|
||||||
|
{ 11, NodeKind::Int16 },
|
||||||
|
{ 12, NodeKind::Int8 },
|
||||||
|
{ 13, NodeKind::Float },
|
||||||
|
{ 14, NodeKind::Double },
|
||||||
|
{ 15, NodeKind::UInt32 },
|
||||||
|
{ 16, NodeKind::UInt16 },
|
||||||
|
{ 17, NodeKind::UInt8 },
|
||||||
|
{ 18, NodeKind::UTF8 }, // UTF8Text
|
||||||
|
{ 19, NodeKind::UTF16 }, // UTF16Text
|
||||||
|
{ 20, NodeKind::Pointer64 }, // FunctionPtr
|
||||||
|
{ 21, NodeKind::Hex8 }, // Custom (expanded by Size)
|
||||||
|
{ 22, NodeKind::Vec2 },
|
||||||
|
{ 23, NodeKind::Vec3 },
|
||||||
|
{ 24, NodeKind::Vec4 },
|
||||||
|
{ 25, NodeKind::Mat4x4 },
|
||||||
|
{ 26, NodeKind::Pointer64 }, // VTable
|
||||||
|
{ 27, NodeKind::Array }, // ClassInstanceArray
|
||||||
|
// 28: null (used for Class elements, not nodes)
|
||||||
|
{ 29, NodeKind::Pointer64 }, // UTF8TextPtr
|
||||||
|
{ 30, NodeKind::Pointer64 }, // UTF16TextPtr
|
||||||
|
// 31: BitField → UInt8 fallback
|
||||||
|
{ 31, NodeKind::UInt8 },
|
||||||
|
{ 32, NodeKind::UInt64 },
|
||||||
|
{ 33, NodeKind::Pointer64 }, // Function
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2013 / ReClass 2011 type map (31 entries)
|
||||||
|
static const struct { int xmlType; NodeKind kind; } kTypeMap2013[] = {
|
||||||
|
{ 1, NodeKind::Struct }, // ClassInstance
|
||||||
|
{ 4, NodeKind::Hex32 },
|
||||||
|
{ 5, NodeKind::Hex16 },
|
||||||
|
{ 6, NodeKind::Hex8 },
|
||||||
|
{ 7, NodeKind::Pointer64 }, // ClassPointer
|
||||||
|
{ 8, NodeKind::Int32 },
|
||||||
|
{ 9, NodeKind::Int16 },
|
||||||
|
{ 10, NodeKind::Int8 },
|
||||||
|
{ 11, NodeKind::Float },
|
||||||
|
{ 12, NodeKind::UInt32 },
|
||||||
|
{ 13, NodeKind::UInt16 },
|
||||||
|
{ 14, NodeKind::UInt8 },
|
||||||
|
{ 15, NodeKind::UTF8 }, // UTF8Text
|
||||||
|
{ 16, NodeKind::Pointer64 }, // FunctionPtr
|
||||||
|
{ 17, NodeKind::Hex8 }, // Custom
|
||||||
|
{ 18, NodeKind::Vec2 },
|
||||||
|
{ 19, NodeKind::Vec3 },
|
||||||
|
{ 20, NodeKind::Vec4 },
|
||||||
|
{ 21, NodeKind::Mat4x4 },
|
||||||
|
{ 22, NodeKind::Pointer64 }, // VTable
|
||||||
|
{ 23, NodeKind::Array }, // ClassInstanceArray
|
||||||
|
{ 27, NodeKind::Int64 },
|
||||||
|
{ 28, NodeKind::Double },
|
||||||
|
{ 29, NodeKind::UTF16 }, // UTF16Text
|
||||||
|
{ 30, NodeKind::Array }, // ClassPointerArray
|
||||||
|
};
|
||||||
|
|
||||||
|
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) { k = e.kind; break; }
|
||||||
|
} else {
|
||||||
|
for (const auto& e : kTypeMap2013)
|
||||||
|
if (e.xmlType == xmlType) { k = e.kind; break; }
|
||||||
|
}
|
||||||
|
// 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?
|
||||||
|
static bool isPointerType(int xmlType, XmlVersion ver) {
|
||||||
|
if (ver == XmlVersion::V2016)
|
||||||
|
return xmlType == 8 || xmlType == 20 || xmlType == 26 || xmlType == 29 || xmlType == 30 || xmlType == 33;
|
||||||
|
else
|
||||||
|
return xmlType == 7 || xmlType == 16 || xmlType == 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this XML type a ClassInstance (embedded struct)?
|
||||||
|
static bool isClassInstanceType(int xmlType, XmlVersion ver) {
|
||||||
|
if (ver == XmlVersion::V2016) return xmlType == 1;
|
||||||
|
else return xmlType == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this XML type a ClassInstanceArray?
|
||||||
|
static bool isClassInstanceArrayType(int xmlType, XmlVersion ver) {
|
||||||
|
if (ver == XmlVersion::V2016) return xmlType == 27;
|
||||||
|
else return xmlType == 23 || xmlType == 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this XML type a text node?
|
||||||
|
static bool isTextType(int xmlType, XmlVersion ver) {
|
||||||
|
if (ver == XmlVersion::V2016) return xmlType == 18 || xmlType == 19;
|
||||||
|
else return xmlType == 15 || xmlType == 29;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this XML type a UTF16 text node?
|
||||||
|
static bool isUtf16TextType(int xmlType, XmlVersion ver) {
|
||||||
|
if (ver == XmlVersion::V2016) return xmlType == 19;
|
||||||
|
else return xmlType == 29;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this XML type a Custom node (expanded to hex)?
|
||||||
|
static bool isCustomType(int xmlType, XmlVersion ver) {
|
||||||
|
if (ver == XmlVersion::V2016) return xmlType == 21;
|
||||||
|
else return xmlType == 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deferred pointer resolution entry
|
||||||
|
struct PendingRef {
|
||||||
|
uint64_t nodeId;
|
||||||
|
QString className;
|
||||||
|
};
|
||||||
|
|
||||||
|
NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointerSize) {
|
||||||
|
qDebug() << "[ImportXML] Opening file:" << filePath;
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
qDebug() << "[ImportXML] ERROR: Cannot open file";
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("Cannot open file: ") + filePath;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "[ImportXML] File size:" << file.size() << "bytes";
|
||||||
|
|
||||||
|
QXmlStreamReader xml(&file);
|
||||||
|
XmlVersion version = XmlVersion::V2016; // default to 2016 (most common)
|
||||||
|
|
||||||
|
NodeTree tree;
|
||||||
|
tree.baseAddress = 0x00400000;
|
||||||
|
tree.pointerSize = pointerSize;
|
||||||
|
|
||||||
|
// Class name → struct node ID (for pointer resolution)
|
||||||
|
QHash<QString, uint64_t> classIds;
|
||||||
|
// Deferred pointer refs to resolve after all classes are parsed
|
||||||
|
QVector<PendingRef> pendingRefs;
|
||||||
|
|
||||||
|
// Detect version from first comment
|
||||||
|
bool versionDetected = false;
|
||||||
|
|
||||||
|
while (!xml.atEnd()) {
|
||||||
|
xml.readNext();
|
||||||
|
|
||||||
|
// Detect version from XML comments
|
||||||
|
if (!versionDetected && xml.isComment()) {
|
||||||
|
QString comment = xml.text().toString().trimmed();
|
||||||
|
if (comment.contains(QStringLiteral("ReClassEx"), Qt::CaseInsensitive) ||
|
||||||
|
comment.contains(QStringLiteral("MemeClsEx"), Qt::CaseInsensitive) ||
|
||||||
|
comment.contains(QStringLiteral("2016"), Qt::CaseInsensitive) ||
|
||||||
|
comment.contains(QStringLiteral("2015"), Qt::CaseInsensitive)) {
|
||||||
|
version = XmlVersion::V2016;
|
||||||
|
} else if (comment.contains(QStringLiteral("2013"), Qt::CaseInsensitive) ||
|
||||||
|
comment.contains(QStringLiteral("2011"), Qt::CaseInsensitive)) {
|
||||||
|
version = XmlVersion::V2013;
|
||||||
|
}
|
||||||
|
// else keep default V2016
|
||||||
|
versionDetected = true;
|
||||||
|
qDebug() << "[ImportXML] Detected version:" << (version == XmlVersion::V2016 ? "V2016" : "V2013");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xml.isStartElement()) continue;
|
||||||
|
|
||||||
|
if (xml.name() == QStringLiteral("Class")) {
|
||||||
|
// Parse a class element into a root Struct node
|
||||||
|
QString className = xml.attributes().value(QStringLiteral("Name")).toString();
|
||||||
|
QString strOffset = xml.attributes().value(QStringLiteral("strOffset")).toString();
|
||||||
|
|
||||||
|
// Create root struct node (collapsed by default for large files)
|
||||||
|
Node structNode;
|
||||||
|
structNode.kind = NodeKind::Struct;
|
||||||
|
structNode.name = className;
|
||||||
|
structNode.structTypeName = className;
|
||||||
|
structNode.parentId = 0; // root level
|
||||||
|
structNode.offset = 0;
|
||||||
|
structNode.collapsed = true;
|
||||||
|
|
||||||
|
int structIdx = tree.addNode(structNode);
|
||||||
|
uint64_t structId = tree.nodes[structIdx].id;
|
||||||
|
classIds[className] = structId;
|
||||||
|
qDebug() << "[ImportXML] Class:" << className << "id:" << structId;
|
||||||
|
|
||||||
|
// Parse child Node elements
|
||||||
|
int childOffset = 0;
|
||||||
|
while (!xml.atEnd()) {
|
||||||
|
xml.readNext();
|
||||||
|
|
||||||
|
if (xml.isEndElement() && xml.name() == QStringLiteral("Class"))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!xml.isStartElement() || xml.name() != QStringLiteral("Node"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int xmlType = xml.attributes().value(QStringLiteral("Type")).toInt();
|
||||||
|
QString nodeName = xml.attributes().value(QStringLiteral("Name")).toString();
|
||||||
|
int nodeSize = xml.attributes().value(QStringLiteral("Size")).toInt();
|
||||||
|
QString ptrClass = xml.attributes().value(QStringLiteral("Pointer")).toString();
|
||||||
|
QString instClass = xml.attributes().value(QStringLiteral("Instance")).toString();
|
||||||
|
|
||||||
|
qDebug() << "[ImportXML] Node:" << nodeName << "type:" << xmlType
|
||||||
|
<< "size:" << nodeSize << "ptr:" << ptrClass << "inst:" << instClass;
|
||||||
|
|
||||||
|
// Handle Custom type: expand to appropriate hex nodes
|
||||||
|
if (isCustomType(xmlType, version) && nodeSize > 0) {
|
||||||
|
// Pick best-fit hex kind
|
||||||
|
NodeKind hexKind;
|
||||||
|
int hexSize;
|
||||||
|
if (nodeSize >= 8 && nodeSize % 8 == 0) {
|
||||||
|
hexKind = NodeKind::Hex64; hexSize = 8;
|
||||||
|
} else if (nodeSize >= 4 && nodeSize % 4 == 0) {
|
||||||
|
hexKind = NodeKind::Hex32; hexSize = 4;
|
||||||
|
} else if (nodeSize >= 2 && nodeSize % 2 == 0) {
|
||||||
|
hexKind = NodeKind::Hex16; hexSize = 2;
|
||||||
|
} else {
|
||||||
|
hexKind = NodeKind::Hex8; hexSize = 1;
|
||||||
|
}
|
||||||
|
int count = nodeSize / hexSize;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Node n;
|
||||||
|
n.kind = hexKind;
|
||||||
|
n.name = (count == 1) ? nodeName : QString();
|
||||||
|
n.parentId = structId;
|
||||||
|
n.offset = childOffset;
|
||||||
|
tree.addNode(n);
|
||||||
|
childOffset += hexSize;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeKind kind = lookupKind(xmlType, version, pointerSize);
|
||||||
|
|
||||||
|
// Handle ClassInstanceArray: read child <Array> element
|
||||||
|
if (isClassInstanceArrayType(xmlType, version)) {
|
||||||
|
qDebug() << "[ImportXML] -> ClassInstanceArray";
|
||||||
|
int total = xml.attributes().value(QStringLiteral("Total")).toInt();
|
||||||
|
if (total <= 0)
|
||||||
|
total = xml.attributes().value(QStringLiteral("Count")).toInt();
|
||||||
|
if (total <= 0) total = 1;
|
||||||
|
|
||||||
|
// Read child <Array> element for class name
|
||||||
|
QString arrayClassName;
|
||||||
|
while (!xml.atEnd()) {
|
||||||
|
xml.readNext();
|
||||||
|
if (xml.isEndElement() && xml.name() == QStringLiteral("Node"))
|
||||||
|
break;
|
||||||
|
if (xml.isStartElement() && xml.name() == QStringLiteral("Array")) {
|
||||||
|
arrayClassName = xml.attributes().value(QStringLiteral("Name")).toString();
|
||||||
|
int arrayTotal = xml.attributes().value(QStringLiteral("Total")).toInt();
|
||||||
|
if (arrayTotal <= 0)
|
||||||
|
arrayTotal = xml.attributes().value(QStringLiteral("Count")).toInt();
|
||||||
|
if (arrayTotal > 0) total = arrayTotal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Array node wrapping Struct elements
|
||||||
|
Node arrNode;
|
||||||
|
arrNode.kind = NodeKind::Array;
|
||||||
|
arrNode.name = nodeName;
|
||||||
|
arrNode.parentId = structId;
|
||||||
|
arrNode.offset = childOffset;
|
||||||
|
arrNode.arrayLen = total;
|
||||||
|
arrNode.elementKind = NodeKind::Struct;
|
||||||
|
if (!arrayClassName.isEmpty())
|
||||||
|
arrNode.structTypeName = arrayClassName;
|
||||||
|
int arrIdx = tree.addNode(arrNode);
|
||||||
|
uint64_t arrId = tree.nodes[arrIdx].id;
|
||||||
|
|
||||||
|
// Defer ref resolution if array references a class
|
||||||
|
if (!arrayClassName.isEmpty()) {
|
||||||
|
pendingRefs.append({arrId, arrayClassName});
|
||||||
|
}
|
||||||
|
|
||||||
|
childOffset += nodeSize > 0 ? nodeSize : 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node n;
|
||||||
|
n.kind = kind;
|
||||||
|
n.name = nodeName;
|
||||||
|
n.parentId = structId;
|
||||||
|
n.offset = childOffset;
|
||||||
|
|
||||||
|
// Handle text nodes
|
||||||
|
if (isTextType(xmlType, version)) {
|
||||||
|
if (isUtf16TextType(xmlType, version))
|
||||||
|
n.strLen = qMax(1, nodeSize / 2);
|
||||||
|
else
|
||||||
|
n.strLen = qMax(1, nodeSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointer types
|
||||||
|
if (isPointerType(xmlType, version) && !ptrClass.isEmpty()) {
|
||||||
|
qDebug() << "[ImportXML] -> Pointer to class:" << ptrClass;
|
||||||
|
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});
|
||||||
|
childOffset += nodeSize > 0 ? nodeSize : sizeForKind(kind);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle embedded class instance
|
||||||
|
if (isClassInstanceType(xmlType, version)) {
|
||||||
|
QString resolvedClass = instClass.isEmpty() ? ptrClass : instClass;
|
||||||
|
qDebug() << "[ImportXML] -> ClassInstance:" << resolvedClass;
|
||||||
|
n.collapsed = true; // Start collapsed to avoid recursive expansion freeze
|
||||||
|
n.structTypeName = resolvedClass;
|
||||||
|
if (!n.structTypeName.isEmpty()) {
|
||||||
|
int nodeIdx = tree.addNode(n);
|
||||||
|
uint64_t nodeId = tree.nodes[nodeIdx].id;
|
||||||
|
pendingRefs.append({nodeId, n.structTypeName});
|
||||||
|
} else {
|
||||||
|
tree.addNode(n);
|
||||||
|
}
|
||||||
|
childOffset += nodeSize > 0 ? nodeSize : 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.addNode(n);
|
||||||
|
childOffset += nodeSize > 0 ? nodeSize : sizeForKind(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xml.hasError() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) {
|
||||||
|
qDebug() << "[ImportXML] XML parse error at line" << xml.lineNumber() << ":" << xml.errorString();
|
||||||
|
if (errorMsg)
|
||||||
|
*errorMsg = QStringLiteral("XML parse error at line %1: %2")
|
||||||
|
.arg(xml.lineNumber())
|
||||||
|
.arg(xml.errorString());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "[ImportXML] Parsing complete. Total nodes:" << tree.nodes.size()
|
||||||
|
<< "classes:" << classIds.size() << "pending refs:" << pendingRefs.size();
|
||||||
|
|
||||||
|
if (tree.nodes.isEmpty()) {
|
||||||
|
qDebug() << "[ImportXML] ERROR: No classes found";
|
||||||
|
if (errorMsg) *errorMsg = QStringLiteral("No classes found in file");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve deferred pointer/struct references
|
||||||
|
int resolved = 0, unresolved = 0;
|
||||||
|
for (const auto& ref : pendingRefs) {
|
||||||
|
int nodeIdx = tree.indexOfId(ref.nodeId);
|
||||||
|
if (nodeIdx < 0) continue;
|
||||||
|
|
||||||
|
auto it = classIds.find(ref.className);
|
||||||
|
if (it != classIds.end()) {
|
||||||
|
tree.nodes[nodeIdx].refId = it.value();
|
||||||
|
resolved++;
|
||||||
|
} else {
|
||||||
|
qDebug() << "[ImportXML] Unresolved ref:" << ref.className << "for node" << ref.nodeId;
|
||||||
|
unresolved++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "[ImportXML] Refs resolved:" << resolved << "unresolved:" << unresolved;
|
||||||
|
qDebug() << "[ImportXML] Import complete. Returning tree with" << tree.nodes.size() << "nodes";
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
13
src/imports/import_reclass_xml.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
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,
|
||||||
|
int pointerSize = 8);
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
1333
src/imports/import_source.cpp
Normal file
15
src/imports/import_source.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
// Import C/C++ struct definitions from source code into a NodeTree.
|
||||||
|
// 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,
|
||||||
|
int pointerSize = 8);
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
@@ -4,14 +4,20 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define RCX_PLUGIN_EXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define RCX_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
namespace rcx { class Provider; }
|
namespace rcx { class Provider; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin interface for ReclassX
|
* Plugin interface for Reclass
|
||||||
*
|
*
|
||||||
* Plugins are loaded from the "Plugins" folder as DLLs.
|
* Plugins are loaded from the "Plugins" folder as shared libraries.
|
||||||
* Each plugin must export a C function: extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
* Each plugin must export a C function: extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||||
*/
|
*/
|
||||||
class IPlugin {
|
class IPlugin {
|
||||||
public:
|
public:
|
||||||
@@ -60,6 +66,7 @@ struct PluginProcessInfo {
|
|||||||
QString name;
|
QString name;
|
||||||
QString path;
|
QString path;
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
|
bool is32Bit = false;
|
||||||
|
|
||||||
PluginProcessInfo() : pid(0) {}
|
PluginProcessInfo() : pid(0) {}
|
||||||
PluginProcessInfo(uint32_t p, const QString& n, const QString& pth = QString(), const QIcon& i = QIcon())
|
PluginProcessInfo(uint32_t p, const QString& n, const QString& pth = QString(), const QIcon& i = QIcon())
|
||||||
@@ -127,4 +134,4 @@ public:
|
|||||||
// Plugin factory function signature
|
// Plugin factory function signature
|
||||||
typedef IPlugin* (*CreatePluginFunc)();
|
typedef IPlugin* (*CreatePluginFunc)();
|
||||||
|
|
||||||
#define IPLUGIN_IID "com.reclassx.IPlugin/1.0"
|
#define IPLUGIN_IID "com.reclass.IPlugin/1.0"
|
||||||
|
|||||||
13
src/macos_titlebar.h
Normal 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
@@ -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
|
||||||
3127
src/main.cpp
180
src/mainwindow.h
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "controller.h"
|
||||||
|
#include "titlebar.h"
|
||||||
|
#include "pluginmanager.h"
|
||||||
|
#include "scannerpanel.h"
|
||||||
|
#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 <QPushButton>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <Qsci/qsciscintilla.h>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
class McpBridge;
|
||||||
|
class ShimmerLabel;
|
||||||
|
class DockGripWidget;
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
friend class McpBridge;
|
||||||
|
public:
|
||||||
|
explicit MainWindow(QWidget* parent = nullptr);
|
||||||
|
~MainWindow() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void newClass();
|
||||||
|
void newStruct();
|
||||||
|
void newEnum();
|
||||||
|
void selfTest();
|
||||||
|
void openFile();
|
||||||
|
void saveFile();
|
||||||
|
void saveFileAs();
|
||||||
|
void closeFile();
|
||||||
|
|
||||||
|
void addNode();
|
||||||
|
void removeNode();
|
||||||
|
void changeNodeType();
|
||||||
|
void renameNodeAction();
|
||||||
|
void duplicateNodeAction();
|
||||||
|
void splitView();
|
||||||
|
void unsplitView();
|
||||||
|
|
||||||
|
void undo();
|
||||||
|
void redo();
|
||||||
|
void about();
|
||||||
|
void toggleMcp();
|
||||||
|
void setEditorFont(const QString& fontName);
|
||||||
|
void exportCpp();
|
||||||
|
void exportReclassXmlAction();
|
||||||
|
void importFromSource();
|
||||||
|
void importReclassXml();
|
||||||
|
void importPdb();
|
||||||
|
void showTypeAliasesDialog();
|
||||||
|
void editTheme();
|
||||||
|
void showOptionsDialog();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Status bar helpers — separate app / MCP channels
|
||||||
|
void setAppStatus(const QString& text);
|
||||||
|
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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum ViewMode { VM_Reclass, VM_Rendered };
|
||||||
|
|
||||||
|
QMdiArea* m_mdiArea;
|
||||||
|
ShimmerLabel* m_statusLabel;
|
||||||
|
QString m_appStatus;
|
||||||
|
bool m_mcpBusy = false;
|
||||||
|
QTimer* m_mcpClearTimer = nullptr;
|
||||||
|
QButtonGroup* m_viewBtnGroup = nullptr;
|
||||||
|
QPushButton* m_btnReclass = nullptr;
|
||||||
|
QPushButton* m_btnRendered = 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_removeSplitAction = 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;
|
||||||
|
ViewMode viewMode = VM_Reclass;
|
||||||
|
uint64_t lastRenderedRootId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TabState {
|
||||||
|
RcxDocument* doc;
|
||||||
|
RcxController* ctrl;
|
||||||
|
QSplitter* splitter;
|
||||||
|
QVector<SplitPane> panes;
|
||||||
|
int activePaneIdx = 0;
|
||||||
|
};
|
||||||
|
QMap<QMdiSubWindow*, TabState> m_tabs;
|
||||||
|
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
|
||||||
|
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);
|
||||||
|
void updateWindowTitle();
|
||||||
|
|
||||||
|
void setViewMode(ViewMode mode);
|
||||||
|
void updateRenderedView(TabState& tab, SplitPane& pane);
|
||||||
|
void updateAllRenderedPanes(TabState& tab);
|
||||||
|
uint64_t findRootStructForNode(const NodeTree& tree, uint64_t nodeId) const;
|
||||||
|
void setupRenderedSci(QsciScintilla* sci);
|
||||||
|
|
||||||
|
SplitPane createSplitPane(TabState& tab);
|
||||||
|
void applyTheme(const Theme& theme);
|
||||||
|
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;
|
||||||
|
QSortFilterProxyModel* m_workspaceProxy = nullptr;
|
||||||
|
QLineEdit* m_workspaceSearch = nullptr;
|
||||||
|
QLabel* m_dockTitleLabel = nullptr;
|
||||||
|
QToolButton* m_dockCloseBtn = nullptr;
|
||||||
|
DockGripWidget* m_dockGrip = nullptr;
|
||||||
|
void createWorkspaceDock();
|
||||||
|
void rebuildWorkspaceModel();
|
||||||
|
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();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void changeEvent(QEvent* event) override;
|
||||||
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
1346
src/mcp/mcp_bridge.cpp
Normal file
73
src/mcp/mcp_bridge.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include <QObject>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
class McpBridge : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit McpBridge(MainWindow* mainWindow, QObject* parent = nullptr);
|
||||||
|
~McpBridge() override;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
bool isRunning() const { return m_server != nullptr; }
|
||||||
|
|
||||||
|
bool slowMode() const { return m_slowMode; }
|
||||||
|
void setSlowMode(bool v) { m_slowMode = v; }
|
||||||
|
|
||||||
|
// Call from controller refresh / data change to notify MCP clients
|
||||||
|
void notifyTreeChanged();
|
||||||
|
void notifyDataChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
MainWindow* m_mainWindow;
|
||||||
|
QLocalServer* m_server = nullptr;
|
||||||
|
QLocalSocket* m_client = nullptr; // single client for v1
|
||||||
|
QByteArray m_readBuffer;
|
||||||
|
bool m_initialized = false;
|
||||||
|
bool m_slowMode = false;
|
||||||
|
|
||||||
|
// JSON-RPC plumbing
|
||||||
|
void onNewConnection();
|
||||||
|
void onReadyRead();
|
||||||
|
void onDisconnected();
|
||||||
|
void processLine(const QByteArray& line);
|
||||||
|
void sendJson(const QJsonObject& obj);
|
||||||
|
QJsonObject okReply(const QJsonValue& id, const QJsonObject& result);
|
||||||
|
QJsonObject errReply(const QJsonValue& id, int code, const QString& msg);
|
||||||
|
void sendNotification(const QString& method, const QJsonObject& params = {});
|
||||||
|
|
||||||
|
// MCP method handlers
|
||||||
|
QJsonObject handleInitialize(const QJsonValue& id, const QJsonObject& params);
|
||||||
|
QJsonObject handleToolsList(const QJsonValue& id);
|
||||||
|
QJsonObject handleToolsCall(const QJsonValue& id, const QJsonObject& params);
|
||||||
|
|
||||||
|
// Tool implementations
|
||||||
|
QJsonObject toolProjectState(const QJsonObject& args);
|
||||||
|
QJsonObject toolTreeApply(const QJsonObject& args);
|
||||||
|
QJsonObject toolSourceSwitch(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);
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
QJsonObject makeTextResult(const QString& text, bool isError = false);
|
||||||
|
QString resolvePlaceholder(const QString& ref,
|
||||||
|
const QHash<QString, uint64_t>& placeholderMap);
|
||||||
|
|
||||||
|
// Smart tab resolution: tabIndex arg → activeTab → first tab → auto-create
|
||||||
|
MainWindow::TabState* resolveTab(const QJsonObject& args);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
265
src/optionsdialog.cpp
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#include "optionsdialog.h"
|
||||||
|
#include "themes/thememanager.h"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QTreeWidgetItem>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowTitle("Options");
|
||||||
|
setFixedSize(700, 450);
|
||||||
|
|
||||||
|
auto* mainLayout = new QVBoxLayout(this);
|
||||||
|
mainLayout->setSpacing(8);
|
||||||
|
mainLayout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
|
||||||
|
// -- Middle: left column (search + tree) | right column (pages) --
|
||||||
|
auto* middleLayout = new QHBoxLayout;
|
||||||
|
middleLayout->setSpacing(8);
|
||||||
|
|
||||||
|
// Left column: search bar + tree
|
||||||
|
auto* leftColumn = new QVBoxLayout;
|
||||||
|
leftColumn->setSpacing(4);
|
||||||
|
|
||||||
|
m_search = new QLineEdit;
|
||||||
|
m_search->setPlaceholderText("Search Options (Ctrl+E)");
|
||||||
|
m_search->setClearButtonEnabled(true);
|
||||||
|
connect(m_search, &QLineEdit::textChanged, this, &OptionsDialog::filterTree);
|
||||||
|
leftColumn->addWidget(m_search);
|
||||||
|
|
||||||
|
m_tree = new QTreeWidget;
|
||||||
|
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);
|
||||||
|
|
||||||
|
middleLayout->addLayout(leftColumn);
|
||||||
|
|
||||||
|
// Right column: stacked pages with group boxes
|
||||||
|
m_pages = new QStackedWidget;
|
||||||
|
|
||||||
|
// -- General page --
|
||||||
|
auto* generalPage = new QWidget;
|
||||||
|
auto* generalLayout = new QVBoxLayout(generalPage);
|
||||||
|
generalLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
generalLayout->setSpacing(8);
|
||||||
|
|
||||||
|
// Refresh Rate group box
|
||||||
|
auto* refreshGroup = new QGroupBox("Refresh Rate");
|
||||||
|
auto* refreshLayout = new QFormLayout(refreshGroup);
|
||||||
|
refreshLayout->setSpacing(8);
|
||||||
|
refreshLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
||||||
|
|
||||||
|
m_refreshSpin = new QSpinBox;
|
||||||
|
m_refreshSpin->setRange(1, 60000);
|
||||||
|
m_refreshSpin->setSingleStep(50);
|
||||||
|
m_refreshSpin->setValue(current.refreshMs);
|
||||||
|
m_refreshSpin->setSuffix(" ms");
|
||||||
|
m_refreshSpin->setObjectName("refreshSpin");
|
||||||
|
refreshLayout->addRow("Interval:", m_refreshSpin);
|
||||||
|
|
||||||
|
auto* refreshDesc = new QLabel(
|
||||||
|
"How often live memory is re-read and the view is updated, in milliseconds. "
|
||||||
|
"Lower values give faster updates but use more CPU. Default: 660 ms.");
|
||||||
|
refreshDesc->setWordWrap(true);
|
||||||
|
refreshDesc->setContentsMargins(0, 0, 0, 0);
|
||||||
|
refreshLayout->addRow(refreshDesc);
|
||||||
|
|
||||||
|
generalLayout->addWidget(refreshGroup);
|
||||||
|
|
||||||
|
// Visual Experience group box
|
||||||
|
auto* visualGroup = new QGroupBox("Visual Experience");
|
||||||
|
auto* visualLayout = new QFormLayout(visualGroup);
|
||||||
|
visualLayout->setSpacing(8);
|
||||||
|
visualLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
||||||
|
|
||||||
|
m_themeCombo = new QComboBox;
|
||||||
|
auto& tm = ThemeManager::instance();
|
||||||
|
for (const auto& theme : tm.themes())
|
||||||
|
m_themeCombo->addItem(theme.name);
|
||||||
|
m_themeCombo->setCurrentIndex(current.themeIndex);
|
||||||
|
m_themeCombo->setObjectName("themeCombo");
|
||||||
|
visualLayout->addRow("Color theme:", m_themeCombo);
|
||||||
|
|
||||||
|
m_fontCombo = new QComboBox;
|
||||||
|
m_fontCombo->addItem("JetBrains Mono");
|
||||||
|
m_fontCombo->addItem("Consolas");
|
||||||
|
m_fontCombo->setCurrentText(current.fontName);
|
||||||
|
m_fontCombo->setObjectName("fontCombo");
|
||||||
|
visualLayout->addRow("Editor Font:", m_fontCombo);
|
||||||
|
|
||||||
|
m_titleCaseCheck = new QCheckBox("Uppercase menu items");
|
||||||
|
m_titleCaseCheck->setChecked(current.menuBarTitleCase);
|
||||||
|
visualLayout->addRow(m_titleCaseCheck);
|
||||||
|
|
||||||
|
m_showIconCheck = new QCheckBox("Show icon in title bar");
|
||||||
|
m_showIconCheck->setChecked(current.showIcon);
|
||||||
|
visualLayout->addRow(m_showIconCheck);
|
||||||
|
|
||||||
|
generalLayout->addWidget(visualGroup);
|
||||||
|
generalLayout->addStretch();
|
||||||
|
|
||||||
|
m_pages->addWidget(generalPage); // index 0
|
||||||
|
m_pageKeywords[generalItem] = collectPageKeywords(generalPage);
|
||||||
|
|
||||||
|
// -- 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);
|
||||||
|
aiLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
aiLayout->setSpacing(8);
|
||||||
|
|
||||||
|
auto* mcpGroup = new QGroupBox("MCP Server");
|
||||||
|
auto* mcpLayout = new QVBoxLayout(mcpGroup);
|
||||||
|
mcpLayout->setSpacing(4);
|
||||||
|
|
||||||
|
m_autoMcpCheck = new QCheckBox("Auto-start MCP server");
|
||||||
|
m_autoMcpCheck->setChecked(current.autoStartMcp);
|
||||||
|
mcpLayout->addWidget(m_autoMcpCheck);
|
||||||
|
|
||||||
|
auto* mcpDesc = new QLabel(
|
||||||
|
"Automatically start the MCP bridge server when the application launches, "
|
||||||
|
"allowing external AI tools to connect and interact with the editor.");
|
||||||
|
mcpDesc->setWordWrap(true);
|
||||||
|
mcpDesc->setContentsMargins(20, 0, 0, 0);
|
||||||
|
mcpLayout->addWidget(mcpDesc);
|
||||||
|
|
||||||
|
aiLayout->addWidget(mcpGroup);
|
||||||
|
aiLayout->addStretch();
|
||||||
|
|
||||||
|
m_pages->addWidget(aiPage); // index 1
|
||||||
|
m_pageKeywords[aiItem] = collectPageKeywords(aiPage);
|
||||||
|
|
||||||
|
// -- 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
|
||||||
|
m_pageKeywords[generatorItem] = collectPageKeywords(generatorPage);
|
||||||
|
|
||||||
|
middleLayout->addWidget(m_pages, 1);
|
||||||
|
|
||||||
|
mainLayout->addLayout(middleLayout, 1);
|
||||||
|
|
||||||
|
// Tree <-> page connection
|
||||||
|
m_itemPageIndex[generalItem] = 0;
|
||||||
|
m_itemPageIndex[aiItem] = 1;
|
||||||
|
m_itemPageIndex[generatorItem] = 2;
|
||||||
|
connect(m_tree, &QTreeWidget::currentItemChanged, this,
|
||||||
|
[this](QTreeWidgetItem* item, QTreeWidgetItem*) {
|
||||||
|
if (!item) return;
|
||||||
|
auto it = m_itemPageIndex.find(item);
|
||||||
|
if (it != m_itemPageIndex.end())
|
||||||
|
m_pages->setCurrentIndex(it.value());
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- Button box --
|
||||||
|
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
mainLayout->addWidget(buttons);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.autoStartMcp = m_autoMcpCheck->isChecked();
|
||||||
|
r.refreshMs = m_refreshSpin->value();
|
||||||
|
r.generatorAsserts = m_assertCheck->isChecked();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList OptionsDialog::collectPageKeywords(QWidget* page) {
|
||||||
|
QStringList keywords;
|
||||||
|
for (auto* child : page->findChildren<QWidget*>()) {
|
||||||
|
if (auto* label = qobject_cast<QLabel*>(child))
|
||||||
|
keywords << label->text();
|
||||||
|
else if (auto* cb = qobject_cast<QCheckBox*>(child))
|
||||||
|
keywords << cb->text();
|
||||||
|
else if (auto* gb = qobject_cast<QGroupBox*>(child))
|
||||||
|
keywords << gb->title();
|
||||||
|
else if (auto* combo = qobject_cast<QComboBox*>(child)) {
|
||||||
|
for (int i = 0; i < combo->count(); ++i)
|
||||||
|
keywords << combo->itemText(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OptionsDialog::filterTree(const QString& text) {
|
||||||
|
std::function<bool(QTreeWidgetItem*)> filter = [&](QTreeWidgetItem* item) -> bool {
|
||||||
|
bool anyChildVisible = false;
|
||||||
|
for (int i = 0; i < item->childCount(); ++i) {
|
||||||
|
if (filter(item->child(i)))
|
||||||
|
anyChildVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool selfMatch = item->text(0).contains(text, Qt::CaseInsensitive);
|
||||||
|
if (!selfMatch) {
|
||||||
|
for (const auto& kw : m_pageKeywords.value(item)) {
|
||||||
|
if (kw.contains(text, Qt::CaseInsensitive)) {
|
||||||
|
selfMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool visible = selfMatch || anyChildVisible;
|
||||||
|
item->setHidden(!visible);
|
||||||
|
|
||||||
|
if (visible && item->childCount() > 0)
|
||||||
|
item->setExpanded(true);
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < m_tree->topLevelItemCount(); ++i)
|
||||||
|
filter(m_tree->topLevelItem(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
51
src/optionsdialog.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QSpinBox>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
struct OptionsResult {
|
||||||
|
int themeIndex = 0;
|
||||||
|
QString fontName;
|
||||||
|
bool menuBarTitleCase = true;
|
||||||
|
bool showIcon = false;
|
||||||
|
bool autoStartMcp = true;
|
||||||
|
int refreshMs = 660;
|
||||||
|
bool generatorAsserts = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OptionsDialog : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit OptionsDialog(const OptionsResult& current, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
OptionsResult result() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void filterTree(const QString& text);
|
||||||
|
static QStringList collectPageKeywords(QWidget* page);
|
||||||
|
|
||||||
|
QLineEdit* m_search = nullptr;
|
||||||
|
QTreeWidget* m_tree = nullptr;
|
||||||
|
QStackedWidget* m_pages = nullptr;
|
||||||
|
QComboBox* m_themeCombo = nullptr;
|
||||||
|
QComboBox* m_fontCombo = nullptr;
|
||||||
|
QCheckBox* m_titleCaseCheck = nullptr;
|
||||||
|
QCheckBox* m_showIconCheck = nullptr;
|
||||||
|
QCheckBox* m_autoMcpCheck = nullptr;
|
||||||
|
QSpinBox* m_refreshSpin = nullptr;
|
||||||
|
QCheckBox* m_assertCheck = nullptr;
|
||||||
|
|
||||||
|
// searchable keywords per leaf tree item
|
||||||
|
QHash<QTreeWidgetItem*, QStringList> m_pageKeywords;
|
||||||
|
// tree item → stacked widget page index
|
||||||
|
QHash<QTreeWidgetItem*, int> m_itemPageIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
@@ -80,7 +80,7 @@ bool PluginManager::LoadPlugin(const QString& path)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "PluginManager: Loaded plugin:" << plugin->Name() << plugin->Version() << "by" << plugin->Author();
|
qDebug() << "PluginManager: Loaded plugin:" << plugin->Name().c_str() << plugin->Version().c_str() << "by" << plugin->Author().c_str();
|
||||||
|
|
||||||
// Store plugin entry
|
// Store plugin entry
|
||||||
m_entries.append({library, plugin});
|
m_entries.append({library, plugin});
|
||||||
@@ -92,7 +92,8 @@ bool PluginManager::LoadPlugin(const QString& path)
|
|||||||
IProviderPlugin* provider = static_cast<IProviderPlugin*>(plugin);
|
IProviderPlugin* provider = static_cast<IProviderPlugin*>(plugin);
|
||||||
QString name = QString::fromStdString(plugin->Name());
|
QString name = QString::fromStdString(plugin->Name());
|
||||||
QString identifier = name.toLower().replace(" ", "");
|
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;
|
return true;
|
||||||
|
|||||||
@@ -5,12 +5,24 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <tlhelp32.h>
|
#include <tlhelp32.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
#include <QtWin>
|
||||||
|
#endif
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ProcessPicker::ProcessPicker(QWidget *parent)
|
ProcessPicker::ProcessPicker(QWidget *parent)
|
||||||
@@ -19,22 +31,9 @@ ProcessPicker::ProcessPicker(QWidget *parent)
|
|||||||
, m_useCustomList(false)
|
, m_useCustomList(false)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
initUi();
|
||||||
// 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
|
|
||||||
refreshProcessList();
|
refreshProcessList();
|
||||||
|
selectPreferredProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent)
|
ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent)
|
||||||
@@ -43,23 +42,103 @@ ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget
|
|||||||
, m_useCustomList(true)
|
, m_useCustomList(true)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
initUi();
|
||||||
|
ui->refreshButton->setVisible(false);
|
||||||
|
m_allProcesses = customProcesses;
|
||||||
|
applyFilter();
|
||||||
|
selectPreferredProcess();
|
||||||
|
}
|
||||||
|
|
||||||
// Configure table
|
void ProcessPicker::initUi()
|
||||||
ui->processTable->setColumnWidth(0, 80);
|
{
|
||||||
ui->processTable->setColumnWidth(1, 200);
|
// Table configuration
|
||||||
|
ui->processTable->setColumnWidth(0, 80); // PID column
|
||||||
|
ui->processTable->setColumnWidth(1, 200); // Name column
|
||||||
ui->processTable->horizontalHeader()->setStretchLastSection(true);
|
ui->processTable->horizontalHeader()->setStretchLastSection(true);
|
||||||
|
ui->processTable->setSortingEnabled(true);
|
||||||
ui->processTable->setWordWrap(false);
|
ui->processTable->setWordWrap(false);
|
||||||
ui->processTable->setTextElideMode(Qt::ElideLeft);
|
ui->processTable->setTextElideMode(Qt::ElideLeft);
|
||||||
|
ui->processTable->setShowGrid(false);
|
||||||
|
ui->processTable->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + 6);
|
||||||
|
|
||||||
// Connect signals (no refresh button for custom lists)
|
// Signal connections
|
||||||
ui->refreshButton->setVisible(false);
|
connect(ui->refreshButton, &QPushButton::clicked, this, &ProcessPicker::refreshProcessList);
|
||||||
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
|
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
|
||||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
|
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
|
||||||
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
|
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
|
||||||
|
|
||||||
// Use custom process list
|
// Derive theme colors from the global palette (set by applyGlobalTheme)
|
||||||
m_allProcesses = customProcesses;
|
QPalette pal = qApp->palette();
|
||||||
applyFilter();
|
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()
|
ProcessPicker::~ProcessPicker()
|
||||||
@@ -92,7 +171,10 @@ void ProcessPicker::onProcessSelected()
|
|||||||
|
|
||||||
int row = item->row();
|
int row = item->row();
|
||||||
m_selectedPid = ui->processTable->item(row, 0)->data(Qt::EditRole).toUInt();
|
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();
|
accept();
|
||||||
}
|
}
|
||||||
@@ -119,9 +201,6 @@ void ProcessPicker::enumerateProcesses()
|
|||||||
info.pid = pe32.th32ProcessID;
|
info.pid = pe32.th32ProcessID;
|
||||||
info.name = QString::fromWCharArray(pe32.szExeFile);
|
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);
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
|
||||||
if (hProcess)
|
if (hProcess)
|
||||||
{
|
{
|
||||||
@@ -137,7 +216,11 @@ void ProcessPicker::enumerateProcesses()
|
|||||||
SHFILEINFOW sfi = {};
|
SHFILEINFOW sfi = {};
|
||||||
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
||||||
if (sfi.hIcon) {
|
if (sfi.hIcon) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
info.icon = QIcon(QPixmap::fromImage(QImage::fromHICON(sfi.hIcon)));
|
info.icon = QIcon(QPixmap::fromImage(QImage::fromHICON(sfi.hIcon)));
|
||||||
|
#else
|
||||||
|
info.icon = QIcon(QtWin::fromHICON(sfi.hIcon));
|
||||||
|
#endif
|
||||||
DestroyIcon(sfi.hIcon);
|
DestroyIcon(sfi.hIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +229,11 @@ void ProcessPicker::enumerateProcesses()
|
|||||||
{
|
{
|
||||||
info.path = "";
|
info.path = "";
|
||||||
}
|
}
|
||||||
|
// Detect 32-bit (WoW64) process
|
||||||
|
BOOL isWow64 = FALSE;
|
||||||
|
if (IsWow64Process(hProcess, &isWow64) && isWow64)
|
||||||
|
info.is32Bit = true;
|
||||||
|
|
||||||
CloseHandle(hProcess);
|
CloseHandle(hProcess);
|
||||||
|
|
||||||
processes.append(info);
|
processes.append(info);
|
||||||
@@ -155,6 +243,55 @@ void ProcessPicker::enumerateProcesses()
|
|||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
QDir procDir("/proc");
|
||||||
|
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
|
|
||||||
|
for (const QString& entry : entries) {
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = entry.toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) continue;
|
||||||
|
|
||||||
|
// Read process name from /proc/<pid>/comm
|
||||||
|
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||||
|
QFile commFile(commPath);
|
||||||
|
QString procName;
|
||||||
|
if (commFile.open(QIODevice::ReadOnly)) {
|
||||||
|
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||||
|
commFile.close();
|
||||||
|
}
|
||||||
|
if (procName.isEmpty()) continue;
|
||||||
|
|
||||||
|
// Read exe path from /proc/<pid>/exe symlink
|
||||||
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||||
|
QFileInfo exeInfo(exePath);
|
||||||
|
QString resolvedPath;
|
||||||
|
if (exeInfo.exists())
|
||||||
|
resolvedPath = exeInfo.symLinkTarget();
|
||||||
|
|
||||||
|
// Skip if we can't read the process memory
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ProcessInfo info;
|
||||||
|
info.pid = pid;
|
||||||
|
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
|
#else
|
||||||
// Platform not supported
|
// Platform not supported
|
||||||
QMessageBox::warning(this, "Error", "Process enumeration not supported on this platform.");
|
QMessageBox::warning(this, "Error", "Process enumeration not supported on this platform.");
|
||||||
@@ -176,11 +313,16 @@ void ProcessPicker::populateTable(const QList<ProcessInfo>& processes)
|
|||||||
pidItem->setData(Qt::EditRole, (int)proc.pid);
|
pidItem->setData(Qt::EditRole, (int)proc.pid);
|
||||||
ui->processTable->setItem(i, 0, pidItem);
|
ui->processTable->setItem(i, 0, pidItem);
|
||||||
|
|
||||||
// Name column with icon
|
// Name column with icon and architecture indicator
|
||||||
auto* nameItem = new QTableWidgetItem(proc.name);
|
QString displayName = proc.is32Bit
|
||||||
|
? proc.name + QStringLiteral(" (32-bit)")
|
||||||
|
: proc.name;
|
||||||
|
auto* nameItem = new QTableWidgetItem(displayName);
|
||||||
if (!proc.icon.isNull()) {
|
if (!proc.icon.isNull()) {
|
||||||
nameItem->setIcon(proc.icon);
|
nameItem->setIcon(proc.icon);
|
||||||
}
|
}
|
||||||
|
// Store original name for selectedProcessName()
|
||||||
|
nameItem->setData(Qt::UserRole, proc.name);
|
||||||
ui->processTable->setItem(i, 1, nameItem);
|
ui->processTable->setItem(i, 1, nameItem);
|
||||||
|
|
||||||
// Path column with tooltip for full path
|
// Path column with tooltip for full path
|
||||||
@@ -188,6 +330,9 @@ void ProcessPicker::populateTable(const QList<ProcessInfo>& processes)
|
|||||||
pathItem->setToolTip(proc.path); // Show full path on hover
|
pathItem->setToolTip(proc.path); // Show full path on hover
|
||||||
ui->processTable->setItem(i, 2, pathItem);
|
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)
|
void ProcessPicker::filterProcesses(const QString& text)
|
||||||
@@ -218,3 +363,22 @@ void ProcessPicker::applyFilter()
|
|||||||
|
|
||||||
populateTable(filtered);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct ProcessInfo {
|
|||||||
QString name;
|
QString name;
|
||||||
QString path;
|
QString path;
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
|
bool is32Bit = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProcessPicker : public QDialog
|
class ProcessPicker : public QDialog
|
||||||
@@ -34,9 +35,11 @@ private slots:
|
|||||||
void filterProcesses(const QString& text);
|
void filterProcesses(const QString& text);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void initUi();
|
||||||
void enumerateProcesses();
|
void enumerateProcesses();
|
||||||
void populateTable(const QList<ProcessInfo>& processes);
|
void populateTable(const QList<ProcessInfo>& processes);
|
||||||
void applyFilter();
|
void applyFilter();
|
||||||
|
void selectPreferredProcess();
|
||||||
|
|
||||||
Ui::ProcessPicker *ui;
|
Ui::ProcessPicker *ui;
|
||||||
uint32_t m_selectedPid = 0;
|
uint32_t m_selectedPid = 0;
|
||||||
|
|||||||