Compare commits
199 Commits
snapshot-1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d22661446b | ||
|
|
ecb954f9e2 | ||
|
|
747cbd93d8 | ||
|
|
44fbc2e6d6 | ||
|
|
bc94a595c7 | ||
|
|
b4727df3e9 | ||
|
|
b2a81ea687 | ||
|
|
dc6963e0d5 | ||
|
|
cb10bc8a82 | ||
|
|
b5521bd638 | ||
|
|
89d6e1944b | ||
|
|
7528d1bbbb | ||
|
|
4f2288048e | ||
|
|
97b6f55e1f | ||
|
|
6a30e0a402 | ||
|
|
1501a1542c | ||
|
|
4f82b39785 | ||
|
|
009ddc951c | ||
|
|
5921af2b4f | ||
|
|
5ded192990 | ||
|
|
54bee5022b | ||
|
|
5d2d324946 | ||
|
|
5b2cf1ae1f | ||
|
|
f1a36f2ad3 | ||
|
|
665138e688 | ||
|
|
7688bb5b92 | ||
|
|
701e088be8 | ||
|
|
3c0c248d54 | ||
|
|
7af969f6bd | ||
|
|
8ba1fd2492 | ||
|
|
b08736245b | ||
|
|
7f7bbdcc45 | ||
|
|
79b5125229 | ||
|
|
3aeb1a80d5 | ||
|
|
3b7ed682ac | ||
|
|
0582cb286b | ||
|
|
ea85b7a621 | ||
|
|
6c8b7d3d97 | ||
|
|
d1321b5165 | ||
|
|
483f87cfbd | ||
|
|
4d0782db68 | ||
|
|
51de48a6ed | ||
|
|
7b9b140823 | ||
|
|
a21e5a07a8 | ||
|
|
25afbe373b | ||
|
|
6a4cb47ed4 | ||
|
|
431e2b90c9 | ||
|
|
43365c1aff | ||
|
|
596f410b96 | ||
|
|
f0fc85f60f | ||
|
|
70c7404556 | ||
|
|
f27459c21b | ||
|
|
a5abcbeea6 | ||
|
|
7071402319 | ||
|
|
0dc390ed86 | ||
|
|
188c27c6e2 | ||
|
|
81f1e4319f | ||
|
|
3ab6affa5e | ||
|
|
35b3cd9ac1 | ||
|
|
e5938f7e82 | ||
|
|
03c49d19dd | ||
|
|
b7eebedf50 | ||
|
|
9ff456a8d6 | ||
|
|
580f285edd | ||
|
|
d23a6c7656 | ||
|
|
25d8de95b7 | ||
|
|
955db3813a | ||
|
|
f4f203e0f0 | ||
|
|
1d3f1a672a | ||
|
|
da29206bdb | ||
|
|
4986893fca | ||
|
|
17a1fb032e | ||
|
|
8d92957837 | ||
|
|
f981fe456d | ||
|
|
877ceea4c1 | ||
|
|
4160a229c6 | ||
|
|
1e1afc1640 | ||
|
|
f0cf6c549a | ||
|
|
683eab16ee | ||
|
|
b53dea8f9f | ||
|
|
f06abbab79 | ||
|
|
2477591ed2 | ||
|
|
6c13356d6d | ||
|
|
3b273a7ab2 | ||
|
|
3509a0d9dd | ||
|
|
43c3f5a842 | ||
|
|
0697ce4853 | ||
|
|
ed1bfd04cd | ||
|
|
c275eb33c9 | ||
|
|
636176ee8c | ||
|
|
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 |
255
.github/workflows/build.yml
vendored
@@ -2,7 +2,8 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches:
|
||||
- "**"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
@@ -18,54 +19,68 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Qt6
|
||||
- name: Install Qt6 and MinGW
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: '6.8.1'
|
||||
arch: 'win64_msvc2022_64'
|
||||
version: "6.8.1"
|
||||
arch: "win64_mingw"
|
||||
tools: "tools_mingw1310,qt.tools.win64_mingw1310"
|
||||
cache: true
|
||||
aqtversion: '==3.1.21'
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
- name: Configure
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure --exclude-regex "test_editor|test_windbg_provider|test_com_security"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: Reclass-win64-qt6
|
||||
path: |
|
||||
build/Reclass.exe
|
||||
build/ReclassMcpBridge.exe
|
||||
build/Plugins/*.dll
|
||||
build/*.dll
|
||||
build/platforms/
|
||||
build/styles/
|
||||
build/imageformats/
|
||||
build/iconengines/
|
||||
build/themes/
|
||||
build/screenshot.png
|
||||
|
||||
- name: Get date tag
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
id: date
|
||||
shell: bash
|
||||
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Package release zip
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
|
||||
gcc --version
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF \
|
||||
-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
|
||||
cmake --build build
|
||||
|
||||
- name: Install WDK NuGet
|
||||
shell: pwsh
|
||||
run: |
|
||||
nuget install Microsoft.Windows.WDK.x64 -OutputDirectory wdk_pkg
|
||||
$ntddk = Get-ChildItem wdk_pkg -Recurse -Filter "ntddk.h" |
|
||||
Where-Object { $_.DirectoryName -like "*km*" } |
|
||||
Select-Object -First 1
|
||||
if (!$ntddk) { throw "ntddk.h not found in WDK NuGet package" }
|
||||
$kmDir = $ntddk.DirectoryName
|
||||
$incRoot = Split-Path $kmDir -Parent
|
||||
Write-Host "WDK include root: $incRoot"
|
||||
echo "WDK_INC_ROOT=$incRoot" >> $env:GITHUB_ENV
|
||||
$ntos = Get-ChildItem wdk_pkg -Recurse -Filter "ntoskrnl.lib" |
|
||||
Where-Object { $_.DirectoryName -like "*x64*" } |
|
||||
Select-Object -First 1
|
||||
if (!$ntos) { throw "ntoskrnl.lib not found in WDK NuGet package" }
|
||||
$libRoot = Split-Path (Split-Path $ntos.DirectoryName -Parent) -Parent
|
||||
Write-Host "WDK lib root: $libRoot"
|
||||
echo "WDK_LIB_ROOT=$libRoot" >> $env:GITHUB_ENV
|
||||
$specstr = Get-ChildItem wdk_pkg -Recurse -Filter "specstrings.h" |
|
||||
Select-Object -First 1
|
||||
if (!$specstr) { throw "specstrings.h not found in SDK NuGet package" }
|
||||
$sdkIncRoot = Split-Path $specstr.DirectoryName -Parent
|
||||
Write-Host "SDK include root: $sdkIncRoot"
|
||||
echo "SDK_INC_ROOT=$sdkIncRoot" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Build kernel driver
|
||||
shell: cmd
|
||||
run: call plugins\KernelMemory\driver\build_driver.bat
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
- name: Package release zip
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
|
||||
mkdir -p release
|
||||
cp build/Reclass.exe release/
|
||||
cp build/ReclassMcpBridge.exe release/
|
||||
@@ -74,28 +89,22 @@ jobs:
|
||||
cp -r build/styles release/ 2>/dev/null || true
|
||||
cp -r build/imageformats release/ 2>/dev/null || true
|
||||
cp -r build/iconengines release/ 2>/dev/null || true
|
||||
windeployqt --no-translations --no-system-d3d-compiler --no-opengl-sw release/Reclass.exe
|
||||
mkdir -p release/Plugins
|
||||
cp build/Plugins/*.dll release/Plugins/ 2>/dev/null || true
|
||||
cp plugins/KernelMemory/driver/build/rcxdrv.sys release/Plugins/ 2>/dev/null || true
|
||||
cp -r build/themes release/ 2>/dev/null || true
|
||||
cp -r build/examples release/ 2>/dev/null || true
|
||||
cp build/screenshot.png release/ 2>/dev/null || true
|
||||
cd release && 7z a ../Reclass-win64-qt6.zip *
|
||||
|
||||
- name: Upload release asset
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: softprops/action-gh-release@v2
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
tag_name: snapshot-${{ steps.date.outputs.tag }}
|
||||
name: Snapshot ${{ steps.date.outputs.tag }}
|
||||
body: |
|
||||
Automated snapshot from main branch.
|
||||
Commit: ${{ github.sha }}
|
||||
prerelease: false
|
||||
files: Reclass-win64-qt6.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Reclass-win64-qt6
|
||||
path: Reclass-win64-qt6.zip
|
||||
|
||||
linux:
|
||||
needs: windows
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
@@ -106,9 +115,8 @@ jobs:
|
||||
- name: Install Qt6
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: '6.8.1'
|
||||
version: "6.8.1"
|
||||
cache: true
|
||||
aqtversion: '==3.1.21'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -116,15 +124,13 @@ jobs:
|
||||
sudo apt-get install -y ninja-build libgl1-mesa-dev libfuse2 libxcb-cursor0
|
||||
|
||||
- name: Configure
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: xvfb-run ctest --test-dir build --output-on-failure --exclude-regex "test_editor"
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
- name: Create AppImage
|
||||
run: |
|
||||
@@ -138,6 +144,7 @@ jobs:
|
||||
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
|
||||
@@ -161,103 +168,83 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: Reclass-linux64-qt6
|
||||
path: Reclass-linux64-qt6.AppImage
|
||||
|
||||
- name: Get date tag
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
id: date
|
||||
shell: bash
|
||||
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload release asset
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
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: Reclass-linux64-qt6.AppImage
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
windows-qt5:
|
||||
needs: linux
|
||||
runs-on: windows-latest
|
||||
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 Qt5
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: '5.15.2'
|
||||
arch: 'win64_msvc2019_64'
|
||||
cache: true
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install cmake ninja qt
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: x64
|
||||
- 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
|
||||
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 --exclude-regex "test_editor|test_windbg_provider|test_com_security|test_format|test_command_row|test_type_selector"
|
||||
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
|
||||
if: always()
|
||||
with:
|
||||
name: Reclass-win64-qt5
|
||||
path: |
|
||||
build/Reclass.exe
|
||||
build/ReclassMcpBridge.exe
|
||||
build/Plugins/*.dll
|
||||
build/*.dll
|
||||
build/platforms/
|
||||
build/styles/
|
||||
build/imageformats/
|
||||
build/iconengines/
|
||||
build/themes/
|
||||
build/screenshot.png
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ matrix.zip_name }}
|
||||
|
||||
release:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: [windows, linux, macos]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Get date tag
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
id: date
|
||||
shell: bash
|
||||
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Package release zip
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
shell: bash
|
||||
run: |
|
||||
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
|
||||
mkdir -p release/Plugins
|
||||
cp build/Plugins/*.dll release/Plugins/ 2>/dev/null || true
|
||||
cp -r build/themes release/ 2>/dev/null || true
|
||||
cp build/screenshot.png release/ 2>/dev/null || true
|
||||
cd release && 7z a ../Reclass-win64-qt5.zip *
|
||||
|
||||
- name: Upload release asset
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: snapshot-${{ steps.date.outputs.tag }}
|
||||
@@ -266,6 +253,10 @@ jobs:
|
||||
Automated snapshot from main branch.
|
||||
Commit: ${{ github.sha }}
|
||||
prerelease: false
|
||||
files: Reclass-win64-qt5.zip
|
||||
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
|
||||
.vs/
|
||||
CMakeUserPresets.json
|
||||
plugins/RcNetPluginCompatLayer/bridge/obj
|
||||
plugins/RcNetPluginCompatLayer/bridge/bin
|
||||
.cache
|
||||
*.DS_Store
|
||||
|
||||
6
.gitmodules
vendored
@@ -1,3 +1,9 @@
|
||||
[submodule "third_party/qscintilla"]
|
||||
path = third_party/qscintilla
|
||||
url = https://github.com/brCreate/QScintilla.git
|
||||
[submodule "third_party/raw_pdb"]
|
||||
path = third_party/raw_pdb
|
||||
url = https://github.com/MolecularMatters/raw_pdb.git
|
||||
[submodule "third_party/fadec"]
|
||||
path = third_party/fadec
|
||||
url = https://github.com/aengelke/fadec.git
|
||||
|
||||
547
CMakeLists.txt
@@ -1,8 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(Reclass VERSION 0.1 LANGUAGES CXX)
|
||||
project(Reclass VERSION 0.1 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
@@ -21,6 +22,32 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${_QT_COMPONENTS})
|
||||
set(QT Qt${QT_VERSION_MAJOR})
|
||||
message(STATUS "Using ${QT}: ${${QT}_DIR}")
|
||||
|
||||
# ── ABI sanity check: prevent MSVC ↔ MinGW Qt mismatch ──
|
||||
# Building with MSVC against MinGW Qt (or vice versa) compiles fine but
|
||||
# crashes immediately at runtime (ABI mismatch in QString/QSettings internals).
|
||||
if(MSVC AND "${${QT}_DIR}" MATCHES "mingw")
|
||||
message(FATAL_ERROR
|
||||
"Qt installation was built with MinGW but this project is being compiled with MSVC.\n"
|
||||
" Qt found at: ${${QT}_DIR}\n"
|
||||
"This will compile but crash at startup due to ABI mismatch.\n"
|
||||
"Fix: install Qt for MSVC (e.g. msvc2019_64) and set CMAKE_PREFIX_PATH to it:\n"
|
||||
" cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.5.2/msvc2019_64 ..")
|
||||
elseif(MINGW AND "${${QT}_DIR}" MATCHES "msvc")
|
||||
message(FATAL_ERROR
|
||||
"Qt installation was built with MSVC but this project is being compiled with MinGW.\n"
|
||||
" Qt found at: ${${QT}_DIR}\n"
|
||||
"This will compile but crash at startup due to ABI mismatch.\n"
|
||||
"Fix: install Qt for MinGW and set CMAKE_PREFIX_PATH to it:\n"
|
||||
" cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.5.2/mingw_64 ..")
|
||||
endif()
|
||||
|
||||
# ── MSVC compile flags ──
|
||||
if(MSVC)
|
||||
# /utf-8: treat source and execution character sets as UTF-8
|
||||
# /MP: multi-processor compilation
|
||||
add_compile_options(/utf-8 /MP)
|
||||
endif()
|
||||
|
||||
# Qt5 on Windows needs WinExtras for HICON conversion
|
||||
set(_QT_WINEXTRAS "")
|
||||
if(QT_VERSION_MAJOR EQUAL 5 AND WIN32)
|
||||
@@ -30,6 +57,40 @@ endif()
|
||||
|
||||
find_package(QScintilla REQUIRED)
|
||||
|
||||
# RawPDB — direct PDB file reader (no DIA SDK / msdia140.dll dependency)
|
||||
file(GLOB RAW_PDB_SRCS third_party/raw_pdb/src/*.cpp)
|
||||
add_library(raw_pdb STATIC ${RAW_PDB_SRCS})
|
||||
target_include_directories(raw_pdb PUBLIC third_party/raw_pdb/src)
|
||||
target_compile_features(raw_pdb PRIVATE cxx_std_11)
|
||||
# PDB_CRT.h forward-declares printf/memcmp/etc with __cdecl which conflicts
|
||||
# with non-MSVC compilers (GCC, Clang, MinGW). Force-include a prefix header
|
||||
# that pulls in the real CRT headers and strips __cdecl.
|
||||
if(NOT MSVC)
|
||||
target_compile_options(raw_pdb PUBLIC
|
||||
-include "${CMAKE_CURRENT_SOURCE_DIR}/cmake/raw_pdb_prefix.h")
|
||||
endif()
|
||||
if(WIN32)
|
||||
target_link_libraries(raw_pdb PRIVATE rpcrt4)
|
||||
endif()
|
||||
|
||||
# Fadec — generate decode tables (.inc files) from instrs.txt at configure time
|
||||
find_package(Python3 3.9 REQUIRED)
|
||||
set(FADEC_DIR "${CMAKE_SOURCE_DIR}/third_party/fadec")
|
||||
if(NOT EXISTS "${FADEC_DIR}/fadec-decode-public.inc")
|
||||
message(STATUS "Generating fadec decode tables...")
|
||||
execute_process(
|
||||
COMMAND ${Python3_EXECUTABLE} "${FADEC_DIR}/parseinstrs.py" decode
|
||||
"${FADEC_DIR}/instrs.txt"
|
||||
"${FADEC_DIR}/fadec-decode-public.inc"
|
||||
"${FADEC_DIR}/fadec-decode-private.inc"
|
||||
--32 --64
|
||||
RESULT_VARIABLE _fadec_result
|
||||
)
|
||||
if(NOT _fadec_result EQUAL 0)
|
||||
message(FATAL_ERROR "Failed to generate fadec decode tables")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(Reclass
|
||||
src/main.cpp
|
||||
src/editor.h
|
||||
@@ -59,17 +120,57 @@ add_executable(Reclass
|
||||
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/startpage.h
|
||||
src/dock_tab_buttons.h
|
||||
src/optionsdialog.h
|
||||
src/optionsdialog.cpp
|
||||
src/titlebar.h
|
||||
src/titlebar.cpp
|
||||
src/macos_titlebar.h
|
||||
$<$<PLATFORM_ID:Darwin>:src/macos_titlebar.mm>
|
||||
src/mcp/mcp_bridge.h
|
||||
src/mcp/mcp_bridge.cpp
|
||||
src/addressparser.h
|
||||
src/addressparser.cpp
|
||||
src/symbolstore.h
|
||||
src/symbolstore.cpp
|
||||
src/symbol_downloader.h
|
||||
src/symbol_downloader.cpp
|
||||
src/imports/pe_debug_info.h
|
||||
src/imports/pe_debug_info.cpp
|
||||
src/disasm.h
|
||||
src/disasm.cpp
|
||||
third_party/fadec/decode.c
|
||||
third_party/fadec/format.c
|
||||
$<$<PLATFORM_ID:Windows>:src/app.rc>
|
||||
)
|
||||
|
||||
target_include_directories(Reclass 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_include_directories(Reclass PRIVATE src third_party/fadec)
|
||||
|
||||
target_link_libraries(Reclass PRIVATE
|
||||
${QT}::Widgets
|
||||
@@ -81,30 +182,101 @@ target_link_libraries(Reclass PRIVATE
|
||||
${_QT_WINEXTRAS}
|
||||
)
|
||||
if(WIN32)
|
||||
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi)
|
||||
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi raw_pdb)
|
||||
|
||||
# Copy Debugging Tools dbghelp.dll next to Reclass.exe so the Windows
|
||||
# loader picks it up (app dir > System32). The system dbghelp.dll
|
||||
# lacks StackWalk2 which the tools dbgeng.dll needs for remote debug.
|
||||
set(_DBG_TOOLS_DIRS
|
||||
"C:/Program Files (x86)/Windows Kits/10/Debuggers/x64"
|
||||
"C:/Program Files/Windows Kits/10/Debuggers/x64")
|
||||
foreach(_dir ${_DBG_TOOLS_DIRS})
|
||||
if(EXISTS "${_dir}/dbghelp.dll")
|
||||
foreach(_dll dbghelp.dll dbgcore.dll symsrv.dll)
|
||||
if(EXISTS "${_dir}/${_dll}")
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${_dir}/${_dll}" $<TARGET_FILE_DIR:Reclass>
|
||||
COMMENT "Copying ${_dll} from Debugging Tools")
|
||||
endif()
|
||||
endforeach()
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp)
|
||||
target_link_libraries(ReclassMcpBridge PRIVATE ${QT}::Core ${QT}::Network)
|
||||
if(APPLE)
|
||||
add_custom_command(TARGET ReclassMcpBridge POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:ReclassMcpBridge>
|
||||
$<TARGET_FILE_DIR:Reclass>/ReclassMcpBridge
|
||||
COMMENT "Bundling ReclassMcpBridge into Reclass.app"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Copy built-in theme JSON files to build directory
|
||||
# Copy built-in theme JSON files next to the executable.
|
||||
# For single-config generators (Ninja/Make) the exe is in ${CMAKE_BINARY_DIR},
|
||||
# for multi-config generators (MSVC/Xcode) it's in ${CMAKE_BINARY_DIR}/<config>.
|
||||
# Using a post-build copy with $<TARGET_FILE_DIR:Reclass> handles both.
|
||||
file(GLOB _theme_files "${CMAKE_SOURCE_DIR}/src/themes/defaults/*.json")
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/themes")
|
||||
|
||||
# Single-config: configure_file for IDE convenience (available before first build)
|
||||
if(NOT CMAKE_CONFIGURATION_TYPES)
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/themes")
|
||||
foreach(_tf ${_theme_files})
|
||||
get_filename_component(_name ${_tf} NAME)
|
||||
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Post-build: always copy to the actual exe directory (works for all generators)
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/themes"
|
||||
COMMENT "Creating themes directory next to executable")
|
||||
foreach(_tf ${_theme_files})
|
||||
get_filename_component(_name ${_tf} NAME)
|
||||
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${_tf}" "$<TARGET_FILE_DIR:Reclass>/themes/${_name}")
|
||||
endforeach()
|
||||
|
||||
if(APPLE)
|
||||
target_sources(Reclass PRIVATE ${_theme_files})
|
||||
set_source_files_properties(${_theme_files}
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/themes")
|
||||
endif()
|
||||
|
||||
# Copy example .rcx files next to the executable (same logic as themes)
|
||||
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
|
||||
|
||||
if(NOT CMAKE_CONFIGURATION_TYPES)
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
|
||||
foreach(_ef ${_example_files})
|
||||
get_filename_component(_name ${_ef} NAME)
|
||||
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/examples"
|
||||
COMMENT "Creating examples directory next to executable")
|
||||
foreach(_ef ${_example_files})
|
||||
get_filename_component(_name ${_ef} NAME)
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${_ef}" "$<TARGET_FILE_DIR:Reclass>/examples/${_name}")
|
||||
endforeach()
|
||||
|
||||
if(APPLE)
|
||||
target_sources(Reclass PRIVATE ${_example_files})
|
||||
set_source_files_properties(${_example_files}
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/examples")
|
||||
endif()
|
||||
|
||||
include(deploy)
|
||||
|
||||
if(TARGET deploy)
|
||||
add_custom_target(screenshot ALL
|
||||
COMMAND Reclass --screenshot ${CMAKE_BINARY_DIR}/screenshot.png
|
||||
DEPENDS Reclass deploy
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Capturing UI screenshot with class open..."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(_combine_script "${CMAKE_BINARY_DIR}/combine_sources.cmake")
|
||||
file(WRITE ${_combine_script} "
|
||||
@@ -121,7 +293,7 @@ foreach(_f
|
||||
\"${CMAKE_SOURCE_DIR}/src/generator.cpp\"
|
||||
\"${CMAKE_SOURCE_DIR}/src/main.cpp\")
|
||||
file(READ \${_f} _content)
|
||||
file(APPEND \${_out} \${_content})
|
||||
file(APPEND \${_out} \"\${_content}\")
|
||||
file(APPEND \${_out} \"\\n\")
|
||||
endforeach()
|
||||
message(STATUS \"Combined sources -> \${_out}\")
|
||||
@@ -138,29 +310,31 @@ if(BUILD_TESTING)
|
||||
find_package(${QT} REQUIRED COMPONENTS Test)
|
||||
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_link_libraries(test_core PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_core COMMAND test_core)
|
||||
|
||||
add_executable(test_format tests/test_format.cpp src/format.cpp)
|
||||
add_executable(test_typeinfer tests/test_typeinfer.cpp)
|
||||
target_include_directories(test_typeinfer PRIVATE src)
|
||||
target_link_libraries(test_typeinfer PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_typeinfer COMMAND test_typeinfer)
|
||||
|
||||
add_executable(test_format tests/test_format.cpp src/format.cpp src/addressparser.cpp)
|
||||
target_include_directories(test_format PRIVATE src)
|
||||
target_link_libraries(test_format PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_format COMMAND test_format)
|
||||
|
||||
add_executable(test_compose tests/test_compose.cpp src/compose.cpp src/format.cpp)
|
||||
add_executable(test_compose tests/test_compose.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
|
||||
target_include_directories(test_compose PRIVATE src)
|
||||
target_link_libraries(test_compose PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_compose COMMAND test_compose)
|
||||
|
||||
add_executable(test_editor tests/test_editor.cpp src/editor.cpp src/compose.cpp src/format.cpp src/providerregistry.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_editor PRIVATE src)
|
||||
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_provider tests/test_provider.cpp)
|
||||
target_include_directories(test_provider PRIVATE src)
|
||||
target_link_libraries(test_provider PRIVATE ${QT}::Core ${QT}::Test)
|
||||
@@ -171,123 +345,249 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(test_command_row PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_command_row COMMAND test_command_row)
|
||||
|
||||
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
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_controller PRIVATE src)
|
||||
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/controller.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_validation PRIVATE src)
|
||||
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_generator tests/test_generator.cpp
|
||||
src/generator.cpp src/compose.cpp src/format.cpp)
|
||||
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
|
||||
target_include_directories(test_generator PRIVATE src)
|
||||
target_link_libraries(test_generator PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_generator COMMAND test_generator)
|
||||
|
||||
add_executable(test_context_menu tests/test_context_menu.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||
add_executable(test_import_xml tests/test_import_xml.cpp
|
||||
src/imports/import_reclass_xml.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
|
||||
target_include_directories(test_import_xml PRIVATE src)
|
||||
target_link_libraries(test_import_xml PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_import_xml COMMAND test_import_xml)
|
||||
|
||||
add_executable(test_import_source tests/test_import_source.cpp
|
||||
src/imports/import_source.cpp src/format.cpp src/compose.cpp src/addressparser.cpp)
|
||||
target_include_directories(test_import_source PRIVATE src)
|
||||
target_link_libraries(test_import_source PRIVATE ${QT}::Core ${QT}::Test)
|
||||
add_test(NAME test_import_source COMMAND test_import_source)
|
||||
|
||||
add_executable(test_export_xml tests/test_export_xml.cpp
|
||||
src/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/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_context_menu PRIVATE src)
|
||||
target_link_libraries(test_context_menu PRIVATE
|
||||
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_context_menu PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||
endif()
|
||||
add_test(NAME test_context_menu COMMAND test_context_menu)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_controller PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||
endif()
|
||||
add_test(NAME test_controller COMMAND test_controller)
|
||||
|
||||
add_executable(test_rendered_view tests/test_rendered_view.cpp
|
||||
src/generator.cpp src/compose.cpp src/format.cpp)
|
||||
target_include_directories(test_rendered_view PRIVATE src)
|
||||
target_link_libraries(test_rendered_view PRIVATE
|
||||
add_executable(test_context_menu tests/test_context_menu.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
target_include_directories(test_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/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
target_include_directories(test_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_rendered_view COMMAND test_rendered_view)
|
||||
add_test(NAME test_editor COMMAND test_editor)
|
||||
|
||||
add_executable(test_new_features tests/test_new_features.cpp
|
||||
src/generator.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||
src/editor.cpp src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_new_features PRIVATE src)
|
||||
target_link_libraries(test_new_features PRIVATE
|
||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||
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)
|
||||
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_test(NAME test_rendered_view COMMAND test_rendered_view)
|
||||
|
||||
add_executable(test_type_selector tests/test_type_selector.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||
add_executable(test_type_selector tests/test_type_selector.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_type_selector PRIVATE src)
|
||||
target_link_libraries(test_type_selector PRIVATE
|
||||
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)
|
||||
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_theme tests/test_theme.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_theme PRIVATE src)
|
||||
target_link_libraries(test_theme PRIVATE ${QT}::Widgets ${QT}::Test)
|
||||
add_test(NAME test_theme COMMAND test_theme)
|
||||
add_executable(test_type_visibility tests/test_type_visibility.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
target_include_directories(test_type_visibility PRIVATE src third_party/fadec)
|
||||
target_link_libraries(test_type_visibility PRIVATE
|
||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_type_visibility PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||
endif()
|
||||
add_test(NAME test_type_visibility COMMAND test_type_visibility)
|
||||
|
||||
add_executable(test_options_dialog tests/test_options_dialog.cpp
|
||||
add_executable(test_options_dialog tests/test_options_dialog.cpp
|
||||
src/optionsdialog.cpp src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_options_dialog PRIVATE src)
|
||||
target_link_libraries(test_options_dialog PRIVATE ${QT}::Widgets ${QT}::Test)
|
||||
add_test(NAME test_options_dialog COMMAND test_options_dialog)
|
||||
target_include_directories(test_options_dialog PRIVATE src)
|
||||
target_link_libraries(test_options_dialog PRIVATE ${QT}::Widgets ${QT}::Test)
|
||||
add_test(NAME test_options_dialog COMMAND test_options_dialog)
|
||||
|
||||
if(WIN32)
|
||||
add_executable(test_windbg_provider tests/test_windbg_provider.cpp
|
||||
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp)
|
||||
target_include_directories(test_windbg_provider PRIVATE src plugins/WinDbgMemory)
|
||||
target_link_libraries(test_windbg_provider PRIVATE
|
||||
add_executable(test_source_provider tests/test_source_provider.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS}
|
||||
src/resources.qrc)
|
||||
target_include_directories(test_source_provider PRIVATE src third_party/fadec)
|
||||
target_link_libraries(test_source_provider PRIVATE
|
||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test ${QT}::Svg
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_source_provider PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||
endif()
|
||||
add_test(NAME test_source_provider COMMAND test_source_provider)
|
||||
|
||||
add_executable(test_scanner_ui tests/test_scanner_ui.cpp
|
||||
src/scanner.cpp src/scannerpanel.cpp src/addressparser.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||
target_include_directories(test_scanner_ui PRIVATE src)
|
||||
target_link_libraries(test_scanner_ui PRIVATE
|
||||
${QT}::Widgets ${QT}::Concurrent ${QT}::Test)
|
||||
add_test(NAME test_scanner_ui COMMAND test_scanner_ui)
|
||||
|
||||
add_executable(test_mcp tests/test_mcp.cpp)
|
||||
target_include_directories(test_mcp PRIVATE src)
|
||||
target_link_libraries(test_mcp PRIVATE ${QT}::Core ${QT}::Network ${QT}::Test)
|
||||
add_test(NAME test_mcp COMMAND test_mcp)
|
||||
|
||||
if(WIN32)
|
||||
add_executable(test_windbg_provider tests/test_windbg_provider.cpp
|
||||
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp
|
||||
src/scanner.cpp)
|
||||
target_include_directories(test_windbg_provider PRIVATE src plugins/WinDbgMemory)
|
||||
target_link_libraries(test_windbg_provider PRIVATE
|
||||
${QT}::Widgets ${QT}::Concurrent ${QT}::Test dbgeng ole32)
|
||||
add_test(NAME test_windbg_provider COMMAND test_windbg_provider)
|
||||
endif()
|
||||
add_test(NAME test_windbg_provider COMMAND test_windbg_provider)
|
||||
|
||||
# Standalone test: proves whether CoInitializeSecurity is needed for DebugConnect
|
||||
# Requires a running WinDbg debug server on port 5055
|
||||
if(WIN32)
|
||||
add_executable(test_com_security tests/test_com_security.cpp)
|
||||
target_link_libraries(test_com_security PRIVATE dbgeng ole32 version)
|
||||
add_test(NAME test_com_security COMMAND test_com_security)
|
||||
endif()
|
||||
add_executable(test_kernel_provider tests/test_kernel_provider.cpp
|
||||
plugins/KernelMemory/KernelMemoryPlugin.cpp
|
||||
src/processpicker.cpp src/processpicker.ui
|
||||
src/scanner.cpp)
|
||||
target_include_directories(test_kernel_provider PRIVATE
|
||||
src plugins/KernelMemory)
|
||||
target_link_libraries(test_kernel_provider PRIVATE
|
||||
${QT}::Widgets ${QT}::Concurrent ${QT}::Test
|
||||
psapi shell32 advapi32 ${_QT_WINEXTRAS})
|
||||
add_test(NAME test_kernel_provider COMMAND test_kernel_provider)
|
||||
endif()
|
||||
|
||||
# Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe
|
||||
# that links the broadest set of Qt modules; all test exes share the same output dir)
|
||||
if(TARGET ${QT}::windeployqt)
|
||||
add_custom_target(deploy_tests ALL
|
||||
add_executable(bench_large_class tests/bench_large_class.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
|
||||
src/providerregistry.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
target_include_directories(bench_large_class PRIVATE src third_party/fadec)
|
||||
target_link_libraries(bench_large_class PRIVATE
|
||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(bench_large_class PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||
endif()
|
||||
add_test(NAME bench_large_class COMMAND bench_large_class)
|
||||
|
||||
add_executable(bench_project tests/bench_project.cpp)
|
||||
target_include_directories(bench_project PRIVATE src)
|
||||
target_link_libraries(bench_project PRIVATE ${QT}::Widgets ${QT}::Test)
|
||||
if(WIN32)
|
||||
target_link_libraries(bench_project PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||
endif()
|
||||
add_test(NAME bench_project COMMAND bench_project)
|
||||
|
||||
# Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe
|
||||
# that links the broadest set of Qt modules; all test exes share the same output dir)
|
||||
if(TARGET ${QT}::windeployqt)
|
||||
add_custom_target(deploy_tests ALL
|
||||
COMMAND $<TARGET_FILE:${QT}::windeployqt>
|
||||
--no-compiler-runtime --no-translations
|
||||
--no-opengl-sw --no-system-d3d-compiler
|
||||
@@ -295,9 +595,16 @@ if(BUILD_TESTING)
|
||||
DEPENDS test_controller
|
||||
COMMENT "Deploying Qt runtime DLLs for tests..."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endif() # BUILD_UI_TESTS
|
||||
endif()
|
||||
add_subdirectory(plugins/ProcessMemory)
|
||||
if(WIN32)
|
||||
add_subdirectory(plugins/WinDbgMemory)
|
||||
if(NOT APPLE)
|
||||
add_subdirectory(plugins/RemoteProcessMemory)
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_subdirectory(plugins/KernelMemory)
|
||||
add_subdirectory(plugins/WinDbgMemory)
|
||||
add_subdirectory(plugins/RcNetPluginCompatLayer)
|
||||
endif()
|
||||
|
||||
212
README.md
@@ -1,45 +1,197 @@
|
||||
This tool helps you inspect raw bytes and interpret them as types (structs, arrays, primitives, pointers, padding) instead of just hex. It is essentially a debugging tool for figuring out unknown data structures either runtime or from some static source.
|
||||
<div align="center">
|
||||
|
||||

|
||||
<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>
|
||||
|
||||
## State
|
||||
**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**
|
||||
|
||||
- MCP (Model Context Protocol) bridge via `ReclassMcpBridge.exe`. The server starts by default and can be stopped from the File menu. It exposes all tool functionality to any MCP-compatible client (e.g. Claude Code) and falls back to UI prompts when the client requests something not yet covered by tools. To connect, add this to your MCP client config (e.g. `.mcp.json`):
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"ReclassMcpBridge": {
|
||||
"command": "path/to/build/ReclassMcpBridge.exe",
|
||||
"args": []
|
||||
}
|
||||
[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)
|
||||
- **Kernel driver** — Windows kernel driver (IOCTL) for process memory, physical memory, page table walking, and CR3/VTOP translation
|
||||
- **Remote Process** — read another process's memory over TCP with cross-architecture 32/64-bit support
|
||||
- **WinDbg** — connect to live WinDbg debugging sessions or load crash dumps
|
||||
- **Saved sources** — quick-switch between recently used data sources per tab
|
||||
|
||||
## Plugin System
|
||||
|
||||
DLL plugins loaded from a `Plugins` folder, auto or manual.
|
||||
|
||||
**Bundled plugins:**
|
||||
|
||||
| Plugin | Description |
|
||||
|--------|-------------|
|
||||
| **Process memory** | Attach to local processes on Windows and Linux — PID-based, with symbol resolution and module/region enumeration |
|
||||
| **Kernel memory** | Windows kernel driver (IOCTL) for reading/writing process and physical memory, CR3 queries, virtual-to-physical translation, and full 4-level page table walking — supports 4KB, 2MB, and 1GB pages |
|
||||
| **WinDbg** | Access data from live WinDbg debugging sessions |
|
||||
| **Remote process memory** | TCP RPC-based remote process access with cross-architecture support |
|
||||
| **ReClass.NET compatibility** | Load existing ReClass.NET native DLL plugins directly; optional .NET CLR hosting for managed plugins |
|
||||
|
||||
## MCP Integration
|
||||
|
||||
Built-in [Model Context Protocol](https://modelcontextprotocol.io/) bridge via `ReclassMcpBridge` — the first reverse engineering tool with native AI/LLM integration. The server uses JSON-RPC 2.0 over named pipes and can be toggled from the Tools menu or auto-started on launch.
|
||||
|
||||
**Available tools:**
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `projectState` | Read current tree structure, base address, tab state |
|
||||
| `treeApply` | Apply structural command deltas to the node tree |
|
||||
| `sourceSwitch` | Switch the active data source |
|
||||
| `hexRead` | Read bytes at an address |
|
||||
| `hexWrite` | Write bytes at an address |
|
||||
| `statusSet` | Update the status bar text |
|
||||
| `uiAction` | Trigger menu actions programmatically |
|
||||
| `treeSearch` | Search nodes by name or type |
|
||||
| `nodeHistory` | Query value change history for a node |
|
||||
|
||||
**Notifications:** `notifyTreeChanged`, `notifyDataChanged`
|
||||
|
||||
A standalone stdio-to-pipe bridge binary is built alongside the main application. To connect, add this to your MCP client config (e.g. `.mcp.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"ReclassMcpBridge": {
|
||||
"command": "path/to/build/ReclassMcpBridge",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
1. Prerequisites
|
||||
### Prerequisites
|
||||
|
||||
- Qt 6 with MinGW - Qt Online Installer https://doc.qt.io/qt-6/qt-online-installation.html , note to select MinGW kit + CMake/Ninja from Tools section (online installers index: https://download.qt.io/official_releases/online_installers/)
|
||||
- CMake 3.20+ - https://cmake.org/download/ - bundled with Qt
|
||||
- windeployqt docs - https://doc.qt.io/qt-6/windows-deployment.html
|
||||
- **Qt 6** (or Qt 5) with MinGW — [Qt Online Installer](https://doc.qt.io/qt-6/qt-online-installation.html) (select MinGW kit + CMake/Ninja from the Tools section)
|
||||
- **CMake 3.20+** — [cmake.org](https://cmake.org/download/) (bundled with Qt)
|
||||
- **Ninja** — bundled with the Qt installer
|
||||
|
||||
2. Quick Build (relies on powershell| for manual build skip to step 3)
|
||||
### Quick Build
|
||||
|
||||
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
|
||||
cd Reclass
|
||||
.\scripts\build_qscintilla.ps1
|
||||
.\scripts\build.ps1
|
||||
^ script above tries to autodetect Qt install (as we learned not everyone installs to C:/Qt/)
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
|
||||
cd Reclass
|
||||
.\scripts\build_qscintilla.ps1
|
||||
.\scripts\build.ps1
|
||||
```
|
||||
|
||||
3. Manual Build
|
||||
The build script auto-detects your Qt install location.
|
||||
|
||||
Step by step for peoplewho want to run commands themselves:
|
||||
1. Clone with --recurse-submodules (+ fallback git submodule update --init --recursive)
|
||||
2. Build QScintilla: qmake + mingw32-make in third_party/qscintilla/src
|
||||
3. CMake configure + build with -DCMAKE_PREFIX_PATH
|
||||
4. optionallly windeployqt the exe
|
||||
### macOS Build
|
||||
|
||||
```bash
|
||||
./scripts/build_macos.sh --qt-dir /opt/homebrew/opt/qt --build-type Release --package
|
||||
```
|
||||
|
||||
If you installed Qt via Homebrew, `--qt-dir /opt/homebrew/opt/qt` is typical on Apple Silicon. You can also set `QTDIR` or `Qt6_DIR` instead of passing `--qt-dir`.
|
||||
|
||||
Note: macOS Gatekeeper may block unsigned apps. If the app won't open, go to **System Settings > Privacy & Security** and click **Open Anyway**.
|
||||
|
||||
### Manual Build (MinGW)
|
||||
|
||||
1. Clone with `--recurse-submodules` (or run `git submodule update --init --recursive` after cloning)
|
||||
2. Build QScintilla: `qmake` + `mingw32-make` in `third_party/qscintilla/src`
|
||||
3. Configure and build:
|
||||
```bash
|
||||
cmake -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.x/mingw_64
|
||||
cmake --build build
|
||||
```
|
||||
4. Optionally run `windeployqt` on the output executable
|
||||
|
||||
### Visual Studio 2022+
|
||||
|
||||
The `msvc/` folder contains a ready-made solution (`Reclass.slnx`) with projects for the main application, all plugins, and third-party libraries. Requires the [Qt Visual Studio Tools](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022) extension with a Qt 6 MSVC kit configured.
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
30 tests covering composition, serialization, undo/redo, import/export, provider switching, type visibility, validation, scanning, and rendering.
|
||||
|
||||
## Alternatives
|
||||
|
||||
- ReClass.NET (reclass.net) - https://github.com/ReClassNET/ReClass.NET
|
||||
- ReClassEx - https://github.com/ajkhoury/ReClassEx
|
||||
- [ReClass.NET](https://github.com/ReClassNET/ReClass.NET)
|
||||
- [ReClassEx](https://github.com/ajkhoury/ReClassEx)
|
||||
|
||||
<div align="center">
|
||||
<sub>MIT License</sub>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cmake/deploy.cmake - Dual-mode script for deploying Qt runtime DLLs
|
||||
#
|
||||
# Script mode: cmake -P deploy.cmake <target_exe> <windeployqt>
|
||||
# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target)
|
||||
# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target + post-build)
|
||||
|
||||
if(CMAKE_SCRIPT_MODE_FILE)
|
||||
set(TARGET_EXE ${CMAKE_ARGV3})
|
||||
@@ -17,7 +17,6 @@ if(CMAKE_SCRIPT_MODE_FILE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${WINDEPLOYQT}
|
||||
--pdb
|
||||
--no-compiler-runtime
|
||||
--no-translations
|
||||
--no-opengl-sw
|
||||
@@ -67,6 +66,7 @@ if(NOT TARGET ${QT}::windeployqt AND TARGET ${QT}::qmake)
|
||||
endif()
|
||||
|
||||
if(TARGET ${QT}::windeployqt)
|
||||
# Standalone "deploy" target (can still be invoked manually)
|
||||
add_custom_target(deploy
|
||||
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
|
||||
$<TARGET_FILE:Reclass>
|
||||
@@ -79,4 +79,13 @@ if(TARGET ${QT}::windeployqt)
|
||||
set_target_properties(deploy PROPERTIES
|
||||
ADDITIONAL_CLEAN_FILES $<TARGET_FILE_DIR:Reclass>/.qt_deployed
|
||||
)
|
||||
|
||||
# Auto-deploy as post-build step so the correct Qt DLLs are always next
|
||||
# to the exe. Without this, MSVC builds load whatever Qt DLLs happen to
|
||||
# be in PATH (often MinGW ones), causing instant ABI-mismatch crashes.
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
|
||||
$<TARGET_FILE:Reclass>
|
||||
$<TARGET_FILE:${QT}::windeployqt>
|
||||
COMMENT "Auto-deploying Qt runtime DLLs...")
|
||||
endif()
|
||||
|
||||
29
cmake/raw_pdb_prefix.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Force-included before every raw_pdb translation unit (and consumers).
|
||||
// PDB_CRT.h forward-declares printf/memcmp/etc with extern "C" __cdecl,
|
||||
// which conflicts with MinGW's CRT headers (C++ linkage, no __cdecl).
|
||||
//
|
||||
// Fix: include the real CRT headers, then include PDB_CRT.h with function
|
||||
// names macro-renamed to harmless dummies. This triggers #pragma once so
|
||||
// no raw_pdb source file ever processes PDB_CRT.h's conflicting declarations.
|
||||
//
|
||||
// Guarded with __cplusplus because PUBLIC propagation applies this to C
|
||||
// sources (fadec) where PDB_CRT.h is irrelevant and <cstdio> doesn't exist.
|
||||
#ifdef __cplusplus
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#undef __cdecl
|
||||
#define __cdecl
|
||||
|
||||
#define printf _pdb_crt_unused_printf
|
||||
#define memcmp _pdb_crt_unused_memcmp
|
||||
#define memcpy _pdb_crt_unused_memcpy
|
||||
#define strlen _pdb_crt_unused_strlen
|
||||
#define strcmp _pdb_crt_unused_strcmp
|
||||
#include "Foundation/PDB_CRT.h"
|
||||
#undef printf
|
||||
#undef memcmp
|
||||
#undef memcpy
|
||||
#undef strlen
|
||||
#undef strcmp
|
||||
#endif
|
||||
BIN
docs/README_PIC1.png
Normal file
|
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 |
BIN
docs/README_PIC4.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/README_PIC5.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
docs/README_PIC6.png
Normal file
|
After Width: | Height: | Size: 312 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>
|
||||
211
msvc/Reclass.vcxproj
Normal file
@@ -0,0 +1,211 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="18.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C369F1FE-37C2-4C66-AC6D-ECB2B2B4AD5E}</ProjectGuid>
|
||||
<Keyword>QtVS_v304</Keyword>
|
||||
<WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0</WindowsTargetPlatformVersion>
|
||||
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
|
||||
<Import Project="$(QtMsBuild)\qt_defaults.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
|
||||
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
|
||||
<QtModules>core;gui;widgets;concurrent;network;svg</QtModules>
|
||||
<QtBuildConfig>debug</QtBuildConfig>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
|
||||
<QtInstall>Qt 6.10.2 MSVC</QtInstall>
|
||||
<QtModules>core;gui;widgets;concurrent;network;svg</QtModules>
|
||||
<QtBuildConfig>release</QtBuildConfig>
|
||||
</PropertyGroup>
|
||||
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
|
||||
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
|
||||
</Target>
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Label="Shared" />
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="$(QtMsBuild)\Qt.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="$(QtMsBuild)\Qt.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\third_party\fadec\;..\third_party\raw_pdb\src\;..\third_party\qscintilla\src\;..\src\</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>dwmapi.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe
|
||||
xcopy /Y /I "$(SolutionDir)..\src\examples\*.rcx" "$(SolutionDir)$(Platform)\$(Configuration)\examples\"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Link>
|
||||
<AdditionalDependencies>dwmapi.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\third_party\fadec\;..\third_party\raw_pdb\src\;..\third_party\qscintilla\src\;..\src\</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<PostBuildEvent>
|
||||
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe
|
||||
xcopy /Y /I "$(SolutionDir)..\src\examples\*.rcx" "$(SolutionDir)$(Platform)\$(Configuration)\examples\"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
|
||||
<ClCompile>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
|
||||
<ClCompile>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<QtRcc Include="..\src\resources.qrc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUic Include="..\src\processpicker.ui" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtMoc Include="..\src\controller.h" />
|
||||
<QtMoc Include="..\src\editor.h" />
|
||||
<QtMoc Include="..\src\mainwindow.h" />
|
||||
<QtMoc Include="..\src\optionsdialog.h" />
|
||||
<QtMoc Include="..\src\processpicker.h" />
|
||||
<QtMoc Include="..\src\scanner.h" />
|
||||
<QtMoc Include="..\src\scannerpanel.h" />
|
||||
<QtMoc Include="..\src\titlebar.h" />
|
||||
<QtMoc Include="..\src\typeselectorpopup.h" />
|
||||
<QtMoc Include="..\src\imports\import_pdb_dialog.h" />
|
||||
<QtMoc Include="..\src\mcp\mcp_bridge.h" />
|
||||
<QtMoc Include="..\src\themes\themeeditor.h" />
|
||||
<QtMoc Include="..\src\themes\thememanager.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\src\addressparser.h" />
|
||||
<ClInclude Include="..\src\core.h" />
|
||||
<ClInclude Include="..\src\disasm.h" />
|
||||
<QtMoc Include="..\src\dock_tab_buttons.h" />
|
||||
<ClInclude Include="..\src\generator.h" />
|
||||
<ClInclude Include="..\src\iplugin.h" />
|
||||
<ClInclude Include="..\src\pluginmanager.h" />
|
||||
<ClInclude Include="..\src\providerregistry.h" />
|
||||
<QtMoc Include="..\src\startpage.h" />
|
||||
<ClInclude Include="..\src\workspace_model.h" />
|
||||
<ClInclude Include="..\src\imports\export_reclass_xml.h" />
|
||||
<ClInclude Include="..\src\imports\import_pdb.h" />
|
||||
<ClInclude Include="..\src\imports\import_reclass_xml.h" />
|
||||
<ClInclude Include="..\src\imports\import_source.h" />
|
||||
<ClInclude Include="..\src\providers\buffer_provider.h" />
|
||||
<ClInclude Include="..\src\providers\null_provider.h" />
|
||||
<ClInclude Include="..\src\providers\provider.h" />
|
||||
<ClInclude Include="..\src\providers\snapshot_provider.h" />
|
||||
<ClInclude Include="..\src\themes\theme.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\addressparser.cpp" />
|
||||
<ClCompile Include="..\src\compose.cpp" />
|
||||
<ClCompile Include="..\src\controller.cpp" />
|
||||
<ClCompile Include="..\src\disasm.cpp" />
|
||||
<ClCompile Include="..\src\editor.cpp" />
|
||||
<ClCompile Include="..\src\format.cpp" />
|
||||
<ClCompile Include="..\src\generator.cpp" />
|
||||
<ClCompile Include="..\src\main.cpp">
|
||||
<DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">input</DynamicSource>
|
||||
<QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(Filename).moc</QtMocFileName>
|
||||
<DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource>
|
||||
<QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(Filename).moc</QtMocFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\optionsdialog.cpp" />
|
||||
<ClCompile Include="..\src\pluginmanager.cpp" />
|
||||
<ClCompile Include="..\src\processpicker.cpp" />
|
||||
<ClCompile Include="..\src\providerregistry.cpp" />
|
||||
<ClCompile Include="..\src\scanner.cpp" />
|
||||
<ClCompile Include="..\src\scannerpanel.cpp" />
|
||||
<ClCompile Include="..\src\titlebar.cpp" />
|
||||
<ClCompile Include="..\src\typeselectorpopup.cpp" />
|
||||
<ClCompile Include="..\src\imports\export_reclass_xml.cpp" />
|
||||
<ClCompile Include="..\src\imports\import_pdb.cpp" />
|
||||
<ClCompile Include="..\src\imports\import_pdb_dialog.cpp" />
|
||||
<ClCompile Include="..\src\imports\import_reclass_xml.cpp" />
|
||||
<ClCompile Include="..\src\imports\import_source.cpp" />
|
||||
<ClCompile Include="..\src\mcp\mcp_bridge.cpp" />
|
||||
<ClCompile Include="..\src\themes\theme.cpp" />
|
||||
<ClCompile Include="..\src\themes\themeeditor.cpp" />
|
||||
<ClCompile Include="..\src\themes\thememanager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="fadec.vcxproj">
|
||||
<Project>{6A30A4F0-1A8D-4C6E-82D4-0A0D9693AA40}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="qscintilla.vcxproj">
|
||||
<Project>{F7124B57-7682-4702-B725-4D844DC41ADA}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\third_party\raw_pdb\build\RawPDB.vcxproj">
|
||||
<Project>{fbe3dbfa-20a7-4f99-9326-ed82c8b7b910}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
|
||||
<Import Project="$(QtMsBuild)\qt.targets" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
229
msvc/Reclass.vcxproj.filters
Normal file
@@ -0,0 +1,229 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\imports">
|
||||
<UniqueIdentifier>{A1B2C3D4-0001-0001-0001-000000000001}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\mcp">
|
||||
<UniqueIdentifier>{A1B2C3D4-0001-0001-0001-000000000002}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\themes">
|
||||
<UniqueIdentifier>{A1B2C3D4-0001-0001-0001-000000000003}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\imports">
|
||||
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000001}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\mcp">
|
||||
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000002}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\providers">
|
||||
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000003}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\themes">
|
||||
<UniqueIdentifier>{A1B2C3D4-0002-0001-0001-000000000004}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Form Files">
|
||||
<UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier>
|
||||
<Extensions>ui</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtRcc Include="..\src\resources.qrc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</QtRcc>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUic Include="..\src\processpicker.ui">
|
||||
<Filter>Form Files</Filter>
|
||||
</QtUic>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtMoc Include="..\src\controller.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\editor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\mainwindow.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\optionsdialog.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\processpicker.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\scanner.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\scannerpanel.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\titlebar.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\typeselectorpopup.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\imports\import_pdb_dialog.h">
|
||||
<Filter>Header Files\imports</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\mcp\mcp_bridge.h">
|
||||
<Filter>Header Files\mcp</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\themes\themeeditor.h">
|
||||
<Filter>Header Files\themes</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\themes\thememanager.h">
|
||||
<Filter>Header Files\themes</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\dock_tab_buttons.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="..\src\startpage.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</QtMoc>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\src\addressparser.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\core.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\disasm.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\generator.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\iplugin.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\pluginmanager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\providerregistry.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\workspace_model.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\imports\export_reclass_xml.h">
|
||||
<Filter>Header Files\imports</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\imports\import_pdb.h">
|
||||
<Filter>Header Files\imports</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\imports\import_reclass_xml.h">
|
||||
<Filter>Header Files\imports</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\imports\import_source.h">
|
||||
<Filter>Header Files\imports</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\providers\buffer_provider.h">
|
||||
<Filter>Header Files\providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\providers\null_provider.h">
|
||||
<Filter>Header Files\providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\providers\provider.h">
|
||||
<Filter>Header Files\providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\providers\snapshot_provider.h">
|
||||
<Filter>Header Files\providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\themes\theme.h">
|
||||
<Filter>Header Files\themes</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\addressparser.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\compose.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\controller.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\disasm.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\editor.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\format.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\generator.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\optionsdialog.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\pluginmanager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\processpicker.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\providerregistry.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\scanner.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\scannerpanel.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\titlebar.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\typeselectorpopup.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\imports\export_reclass_xml.cpp">
|
||||
<Filter>Source Files\imports</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\imports\import_pdb.cpp">
|
||||
<Filter>Source Files\imports</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\imports\import_pdb_dialog.cpp">
|
||||
<Filter>Source Files\imports</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\imports\import_reclass_xml.cpp">
|
||||
<Filter>Source Files\imports</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\imports\import_source.cpp">
|
||||
<Filter>Source Files\imports</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\mcp\mcp_bridge.cpp">
|
||||
<Filter>Source Files\mcp</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\themes\theme.cpp">
|
||||
<Filter>Source Files\themes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\themes\themeeditor.cpp">
|
||||
<Filter>Source Files\themes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\themes\thememanager.cpp">
|
||||
<Filter>Source Files\themes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
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>
|
||||
63
plugins/KernelMemory/CMakeLists.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(KernelMemoryPlugin LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Qt is found by the parent project; QT variable (Qt5 or Qt6) is inherited
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC OFF) # run uic manually to avoid dupbuild with ProcessMemoryPlugin
|
||||
|
||||
# ─── Generate ui_processpicker.h in our own build dir ────────────────
|
||||
set(_UI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui")
|
||||
set(_UI_HDR "${CMAKE_CURRENT_BINARY_DIR}/ui_processpicker.h")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${_UI_HDR}"
|
||||
COMMAND ${QT}::uic -o "${_UI_HDR}" "${_UI_SRC}"
|
||||
DEPENDS "${_UI_SRC}"
|
||||
COMMENT "UIC processpicker.ui (KernelMemoryPlugin)"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# ─── Plugin DLL ──────────────────────────────────────────────────────
|
||||
set(PLUGIN_SOURCES
|
||||
KernelMemoryPlugin.h
|
||||
KernelMemoryPlugin.cpp
|
||||
rcx_drv_protocol.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
|
||||
"${_UI_HDR}"
|
||||
)
|
||||
|
||||
add_library(KernelMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||
|
||||
target_link_libraries(KernelMemoryPlugin PRIVATE
|
||||
${QT}::Widgets
|
||||
${_QT_WINEXTRAS}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(KernelMemoryPlugin PRIVATE psapi shell32 advapi32)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_options(KernelMemoryPlugin PRIVATE -fvisibility=hidden)
|
||||
endif()
|
||||
|
||||
target_include_directories(KernelMemoryPlugin PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR} # for ui_processpicker.h
|
||||
)
|
||||
|
||||
set_target_properties(KernelMemoryPlugin PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||
)
|
||||
|
||||
install(TARGETS KernelMemoryPlugin
|
||||
LIBRARY DESTINATION Plugins
|
||||
RUNTIME DESTINATION Plugins
|
||||
)
|
||||
752
plugins/KernelMemory/KernelMemoryPlugin.cpp
Normal file
@@ -0,0 +1,752 @@
|
||||
#include "KernelMemoryPlugin.h"
|
||||
#include "../../src/processpicker.h"
|
||||
|
||||
#include <QStyle>
|
||||
#include <QApplication>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QLibrary>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QtWin>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Helper: DeviceIoControl wrapper
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
static bool ioctlCall(HANDLE h, DWORD code,
|
||||
const void* in, DWORD inLen,
|
||||
void* out, DWORD outLen,
|
||||
DWORD* bytesReturned = nullptr)
|
||||
{
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl(h, code, const_cast<LPVOID>(in), inLen,
|
||||
out, outLen, &br, nullptr);
|
||||
if (bytesReturned) *bytesReturned = br;
|
||||
return ok != FALSE;
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelProcessProvider
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
KernelProcessProvider::KernelProcessProvider(void* driverHandle, uint32_t pid, const QString& processName)
|
||||
: m_driverHandle(driverHandle)
|
||||
, m_pid(pid)
|
||||
, m_processName(processName)
|
||||
{
|
||||
if (m_driverHandle) {
|
||||
queryPeb();
|
||||
cacheModules();
|
||||
}
|
||||
}
|
||||
|
||||
bool KernelProcessProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
if (len > RCX_DRV_MAX_VIRTUAL) len = RCX_DRV_MAX_VIRTUAL;
|
||||
|
||||
RcxDrvReadRequest req{};
|
||||
req.pid = m_pid;
|
||||
req.address = addr;
|
||||
req.length = (uint32_t)len;
|
||||
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl((HANDLE)m_driverHandle,
|
||||
IOCTL_RCX_READ_MEMORY,
|
||||
&req, sizeof(req),
|
||||
buf, (DWORD)len, &br, nullptr);
|
||||
// Zero unread portion (partial copy)
|
||||
if ((int)br < len)
|
||||
memset((char*)buf + br, 0, len - br);
|
||||
return ok || br > 0;
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int KernelProcessProvider::size() const
|
||||
{
|
||||
return m_driverHandle ? 0x10000 : 0;
|
||||
}
|
||||
|
||||
bool KernelProcessProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
if (len > RCX_DRV_MAX_VIRTUAL) return false;
|
||||
|
||||
// Build request: header + inline data
|
||||
QByteArray packet(sizeof(RcxDrvWriteRequest) + len, Qt::Uninitialized);
|
||||
auto* req = reinterpret_cast<RcxDrvWriteRequest*>(packet.data());
|
||||
req->pid = m_pid;
|
||||
req->_pad0 = 0;
|
||||
req->address = addr;
|
||||
req->length = (uint32_t)len;
|
||||
req->_pad1 = 0;
|
||||
memcpy(packet.data() + sizeof(RcxDrvWriteRequest), buf, len);
|
||||
|
||||
return ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_WRITE_MEMORY,
|
||||
packet.constData(), (DWORD)packet.size(),
|
||||
nullptr, 0);
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString KernelProcessProvider::getSymbol(uint64_t addr) const
|
||||
{
|
||||
for (const auto& mod : m_modules) {
|
||||
if (addr >= mod.base && addr < mod.base + mod.size) {
|
||||
uint64_t offset = addr - mod.base;
|
||||
return QStringLiteral("%1+0x%2")
|
||||
.arg(mod.name)
|
||||
.arg(offset, 0, 16, QChar('0'));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t KernelProcessProvider::symbolToAddress(const QString& name) const
|
||||
{
|
||||
for (const auto& mod : m_modules) {
|
||||
if (mod.name.compare(name, Qt::CaseInsensitive) == 0)
|
||||
return mod.base;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> KernelProcessProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return regions;
|
||||
|
||||
RcxDrvQueryRegionsRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
// Allocate generous output buffer for region entries
|
||||
constexpr int kMaxEntries = 8192;
|
||||
QByteArray outBuf(kMaxEntries * sizeof(RcxDrvRegionEntry), Qt::Uninitialized);
|
||||
|
||||
DWORD br = 0;
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_REGIONS,
|
||||
&req, sizeof(req),
|
||||
outBuf.data(), (DWORD)outBuf.size(), &br))
|
||||
return regions;
|
||||
|
||||
int count = (int)(br / sizeof(RcxDrvRegionEntry));
|
||||
auto* entries = reinterpret_cast<const RcxDrvRegionEntry*>(outBuf.constData());
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const auto& e = entries[i];
|
||||
// Only include committed, accessible regions
|
||||
if (!(e.state & 0x1000)) continue; // MEM_COMMIT = 0x1000
|
||||
uint32_t p = e.protect;
|
||||
if (p & 0x01) continue; // PAGE_NOACCESS
|
||||
if (p & 0x100) continue; // PAGE_GUARD
|
||||
|
||||
rcx::MemoryRegion region;
|
||||
region.base = e.base;
|
||||
region.size = e.size;
|
||||
region.readable = true;
|
||||
region.writable = (p & 0x04) || (p & 0x08) || (p & 0x40) || (p & 0x80);
|
||||
region.executable = (p & 0x10) || (p & 0x20) || (p & 0x40) || (p & 0x80);
|
||||
|
||||
// Match module name
|
||||
for (const auto& mod : m_modules) {
|
||||
if (region.base >= mod.base && region.base < mod.base + mod.size) {
|
||||
region.moduleName = mod.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
#endif
|
||||
return regions;
|
||||
}
|
||||
|
||||
void KernelProcessProvider::queryPeb()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
RcxDrvQueryPebRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
RcxDrvQueryPebResponse resp{};
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_PEB,
|
||||
&req, sizeof(req), &resp, sizeof(resp))) {
|
||||
m_peb = resp.pebAddress;
|
||||
if (resp.pointerSize == 4)
|
||||
m_pointerSize = 4;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QVector<rcx::Provider::ThreadInfo> KernelProcessProvider::tebs() const
|
||||
{
|
||||
QVector<ThreadInfo> result;
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return result;
|
||||
|
||||
RcxDrvQueryTebsRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
constexpr int kMaxThreads = 4096;
|
||||
QByteArray outBuf(kMaxThreads * sizeof(RcxDrvTebEntry), Qt::Uninitialized);
|
||||
|
||||
DWORD br = 0;
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_TEBS,
|
||||
&req, sizeof(req),
|
||||
outBuf.data(), (DWORD)outBuf.size(), &br))
|
||||
return result;
|
||||
|
||||
int count = (int)(br / sizeof(RcxDrvTebEntry));
|
||||
auto* entries = reinterpret_cast<const RcxDrvTebEntry*>(outBuf.constData());
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.push_back(ThreadInfo{entries[i].tebAddress, entries[i].threadId});
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void KernelProcessProvider::cacheModules()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return;
|
||||
|
||||
RcxDrvQueryModulesRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
constexpr int kMaxModules = 1024;
|
||||
QByteArray outBuf(kMaxModules * sizeof(RcxDrvModuleEntry), Qt::Uninitialized);
|
||||
|
||||
DWORD br = 0;
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_MODULES,
|
||||
&req, sizeof(req),
|
||||
outBuf.data(), (DWORD)outBuf.size(), &br))
|
||||
return;
|
||||
|
||||
int count = (int)(br / sizeof(RcxDrvModuleEntry));
|
||||
auto* entries = reinterpret_cast<const RcxDrvModuleEntry*>(outBuf.constData());
|
||||
|
||||
m_modules.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
QString modName = QString::fromUtf16(reinterpret_cast<const char16_t*>(entries[i].name));
|
||||
if (i == 0)
|
||||
m_base = entries[i].base;
|
||||
|
||||
m_modules.push_back(ModuleInfo{modName, entries[i].base, entries[i].size});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelProcessProvider — paging / address translation
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
uint64_t KernelProcessProvider::getCr3() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_cr3Cache) return m_cr3Cache;
|
||||
if (!m_driverHandle) return 0;
|
||||
|
||||
RcxDrvReadCr3Request req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
RcxDrvReadCr3Response resp{};
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_READ_CR3,
|
||||
&req, sizeof(req), &resp, sizeof(resp))) {
|
||||
m_cr3Cache = resp.cr3;
|
||||
return m_cr3Cache;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
rcx::VtopResult KernelProcessProvider::translateAddress(uint64_t va) const
|
||||
{
|
||||
rcx::VtopResult result{};
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return result;
|
||||
|
||||
RcxDrvVtopRequest req{};
|
||||
req.pid = m_pid;
|
||||
req.virtualAddress = va;
|
||||
|
||||
RcxDrvVtopResponse resp{};
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_VTOP,
|
||||
&req, sizeof(req), &resp, sizeof(resp))) {
|
||||
result.physical = resp.physicalAddress;
|
||||
result.pml4e = resp.pml4e;
|
||||
result.pdpte = resp.pdpte;
|
||||
result.pde = resp.pde;
|
||||
result.pte = resp.pte;
|
||||
result.pageSize = resp.pageSize;
|
||||
result.valid = resp.valid != 0;
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(va);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<uint64_t> KernelProcessProvider::readPageTable(uint64_t physAddr, int startIdx, int count) const
|
||||
{
|
||||
QVector<uint64_t> entries;
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return entries;
|
||||
if (startIdx < 0 || startIdx >= 512) return entries;
|
||||
if (count <= 0) return entries;
|
||||
if (startIdx + count > 512) count = 512 - startIdx;
|
||||
|
||||
// Read the full 4KB page table via physical read
|
||||
int byteOffset = startIdx * 8;
|
||||
int byteLen = count * 8;
|
||||
QByteArray buf(byteLen, 0);
|
||||
|
||||
RcxDrvPhysReadRequest req{};
|
||||
req.physAddress = physAddr + byteOffset;
|
||||
req.length = (uint32_t)byteLen;
|
||||
req.width = 0; // memcpy mode
|
||||
|
||||
DWORD br = 0;
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_READ_PHYS,
|
||||
&req, sizeof(req), buf.data(), (DWORD)byteLen, &br)) {
|
||||
entries.resize(count);
|
||||
memcpy(entries.data(), buf.constData(), byteLen);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(physAddr); Q_UNUSED(startIdx); Q_UNUSED(count);
|
||||
#endif
|
||||
return entries;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelPhysProvider
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
KernelPhysProvider::KernelPhysProvider(void* driverHandle, uint64_t baseAddr)
|
||||
: m_driverHandle(driverHandle)
|
||||
, m_baseAddr(baseAddr)
|
||||
{
|
||||
}
|
||||
|
||||
bool KernelPhysProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
|
||||
// Read in 4KB chunks (driver cap)
|
||||
int offset = 0;
|
||||
while (offset < len) {
|
||||
int chunk = qMin(len - offset, (int)RCX_DRV_MAX_PHYSICAL);
|
||||
|
||||
RcxDrvPhysReadRequest req{};
|
||||
req.physAddress = addr + offset;
|
||||
req.length = (uint32_t)chunk;
|
||||
req.width = 0; // memcpy mode
|
||||
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl((HANDLE)m_driverHandle,
|
||||
IOCTL_RCX_READ_PHYS,
|
||||
&req, sizeof(req),
|
||||
(char*)buf + offset, (DWORD)chunk, &br, nullptr);
|
||||
if (!ok && br == 0) {
|
||||
memset((char*)buf + offset, 0, len - offset);
|
||||
return offset > 0;
|
||||
}
|
||||
if ((int)br < chunk)
|
||||
memset((char*)buf + offset + br, 0, chunk - br);
|
||||
offset += chunk;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool KernelPhysProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
|
||||
int offset = 0;
|
||||
while (offset < len) {
|
||||
int chunk = qMin(len - offset, (int)RCX_DRV_MAX_PHYSICAL);
|
||||
|
||||
QByteArray packet(sizeof(RcxDrvPhysWriteRequest) + chunk, Qt::Uninitialized);
|
||||
auto* req = reinterpret_cast<RcxDrvPhysWriteRequest*>(packet.data());
|
||||
req->physAddress = addr + offset;
|
||||
req->length = (uint32_t)chunk;
|
||||
req->width = 0;
|
||||
memcpy(packet.data() + sizeof(RcxDrvPhysWriteRequest), (const char*)buf + offset, chunk);
|
||||
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_WRITE_PHYS,
|
||||
packet.constData(), (DWORD)packet.size(),
|
||||
nullptr, 0))
|
||||
return false;
|
||||
offset += chunk;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelMemoryPlugin
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
KernelMemoryPlugin::KernelMemoryPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
KernelMemoryPlugin::~KernelMemoryPlugin()
|
||||
{
|
||||
stopDriver();
|
||||
}
|
||||
|
||||
QIcon KernelMemoryPlugin::Icon() const
|
||||
{
|
||||
return qApp->style()->standardIcon(QStyle::SP_DriveHDIcon);
|
||||
}
|
||||
|
||||
bool KernelMemoryPlugin::canHandle(const QString& target) const
|
||||
{
|
||||
return target.startsWith(QStringLiteral("km:"))
|
||||
|| target.startsWith(QStringLiteral("phys:"))
|
||||
|| target.startsWith(QStringLiteral("msr:"));
|
||||
}
|
||||
|
||||
std::unique_ptr<rcx::Provider> KernelMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
|
||||
{
|
||||
if (!ensureDriverLoaded(errorMsg))
|
||||
return nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (target.startsWith(QStringLiteral("km:"))) {
|
||||
// km:{pid}:{name}
|
||||
QStringList parts = target.mid(3).split(':');
|
||||
bool ok = false;
|
||||
uint32_t pid = parts[0].toUInt(&ok);
|
||||
if (!ok || pid == 0) {
|
||||
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target: ") + target;
|
||||
return nullptr;
|
||||
}
|
||||
QString name = parts.size() > 1 ? parts[1] : QStringLiteral("PID %1").arg(pid);
|
||||
auto prov = std::make_unique<KernelProcessProvider>((void*)m_driverHandle, pid, name);
|
||||
if (!prov->isValid()) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to read process %1 (PID: %2) via kernel driver.")
|
||||
.arg(name).arg(pid);
|
||||
return nullptr;
|
||||
}
|
||||
return prov;
|
||||
}
|
||||
|
||||
if (target.startsWith(QStringLiteral("phys:"))) {
|
||||
// phys:{baseAddr}
|
||||
bool ok = false;
|
||||
uint64_t baseAddr = target.mid(5).toULongLong(&ok, 16);
|
||||
if (!ok) baseAddr = 0;
|
||||
return std::make_unique<KernelPhysProvider>((void*)m_driverHandle, baseAddr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (errorMsg) *errorMsg = QStringLiteral("Unknown target format: ") + target;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t KernelMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||
{
|
||||
if (target.startsWith(QStringLiteral("phys:"))) {
|
||||
bool ok = false;
|
||||
uint64_t addr = target.mid(5).toULongLong(&ok, 16);
|
||||
return ok ? addr : 0;
|
||||
}
|
||||
// For process mode, the provider discovers base via modules
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool KernelMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
{
|
||||
// Show process picker directly (physical memory is accessed via
|
||||
// context menu "Browse Page Tables" / "Follow Physical Frame" on an
|
||||
// attached kernel process).
|
||||
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
||||
QList<ProcessInfo> processes;
|
||||
for (const auto& pinfo : pluginProcesses) {
|
||||
ProcessInfo info;
|
||||
info.pid = pinfo.pid;
|
||||
info.name = pinfo.name;
|
||||
info.path = pinfo.path;
|
||||
info.icon = pinfo.icon;
|
||||
info.is32Bit = pinfo.is32Bit;
|
||||
processes.append(info);
|
||||
}
|
||||
|
||||
ProcessPicker picker(processes, parent);
|
||||
if (picker.exec() == QDialog::Accepted) {
|
||||
uint32_t pid = picker.selectedProcessId();
|
||||
QString name = picker.selectedProcessName();
|
||||
*target = QStringLiteral("km:%1:%2").arg(pid).arg(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVector<PluginProcessInfo> KernelMemoryPlugin::enumerateProcesses()
|
||||
{
|
||||
QVector<PluginProcessInfo> processes;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE) return processes;
|
||||
|
||||
PROCESSENTRY32W entry;
|
||||
entry.dwSize = sizeof(entry);
|
||||
|
||||
if (Process32FirstW(snapshot, &entry)) {
|
||||
do {
|
||||
PluginProcessInfo info;
|
||||
info.pid = entry.th32ProcessID;
|
||||
info.name = QString::fromWCharArray(entry.szExeFile);
|
||||
|
||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
|
||||
if (hProcess) {
|
||||
wchar_t path[MAX_PATH * 2];
|
||||
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
|
||||
|
||||
if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) {
|
||||
info.path = QString::fromWCharArray(path);
|
||||
|
||||
SHFILEINFOW sfi = {};
|
||||
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
||||
if (sfi.hIcon) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
||||
#else
|
||||
QPixmap pixmap = QtWin::fromHICON(sfi.hIcon);
|
||||
#endif
|
||||
info.icon = QIcon(pixmap);
|
||||
DestroyIcon(sfi.hIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isWow64 = FALSE;
|
||||
if (IsWow64Process(hProcess, &isWow64) && isWow64)
|
||||
info.is32Bit = true;
|
||||
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
|
||||
processes.append(info);
|
||||
} while (Process32NextW(snapshot, &entry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
#endif
|
||||
|
||||
return processes;
|
||||
}
|
||||
|
||||
void KernelMemoryPlugin::populatePluginMenu(QMenu* menu)
|
||||
{
|
||||
if (!m_driverLoaded) return;
|
||||
menu->addAction(QStringLiteral("Unload Kernel Driver"), [this]() { unloadDriver(); });
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Driver service management
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
QString KernelMemoryPlugin::driverPath() const
|
||||
{
|
||||
// Resolve rcxdrv.sys next to the plugin DLL
|
||||
QString pluginDir = QCoreApplication::applicationDirPath() + QStringLiteral("/Plugins");
|
||||
return pluginDir + QStringLiteral("/rcxdrv.sys");
|
||||
}
|
||||
|
||||
bool KernelMemoryPlugin::ensureDriverLoaded(QString* errorMsg)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Already connected?
|
||||
if (m_driverLoaded && m_driverHandle != INVALID_HANDLE_VALUE) {
|
||||
RcxDrvPingResponse ping{};
|
||||
if (ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping)))
|
||||
return true;
|
||||
// Handle went stale — close it and try to reconnect
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
m_driverLoaded = false;
|
||||
}
|
||||
|
||||
// Show wait cursor (SCM + StartService can take seconds on first load)
|
||||
struct WaitCursorGuard {
|
||||
WaitCursorGuard() { QGuiApplication::setOverrideCursor(Qt::WaitCursor); }
|
||||
~WaitCursorGuard() { QGuiApplication::restoreOverrideCursor(); }
|
||||
} waitCursor;
|
||||
|
||||
// Fast path: driver may already be running (previous session, or after disconnect).
|
||||
// Just try to open the device handle directly.
|
||||
m_driverHandle = CreateFileA(RCX_DRV_USERMODE_PATH,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (m_driverHandle != INVALID_HANDLE_VALUE) {
|
||||
RcxDrvPingResponse ping{};
|
||||
if (ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping))) {
|
||||
m_driverLoaded = true;
|
||||
return true;
|
||||
}
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// Slow path: need to install/start the service.
|
||||
QString sysPath = driverPath();
|
||||
if (!QFileInfo::exists(sysPath)) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Driver not found: %1\n\n"
|
||||
"Place rcxdrv.sys in the Plugins folder next to the plugin DLL.").arg(sysPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (!scm) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to open Service Control Manager.\n"
|
||||
"Run Reclass as Administrator to load the kernel driver.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to open existing service first
|
||||
SC_HANDLE svc = OpenServiceW(scm, L"RcxDrv", SERVICE_ALL_ACCESS);
|
||||
if (!svc) {
|
||||
// Service doesn't exist — create it
|
||||
std::wstring wPath = sysPath.toStdWString();
|
||||
svc = CreateServiceW(scm, L"RcxDrv", L"RcxDrv",
|
||||
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
wPath.c_str(),
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
if (!svc) {
|
||||
DWORD err = GetLastError();
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to create driver service (error %1).\n"
|
||||
"Ensure test signing is enabled: bcdedit /set testsigning on").arg(err);
|
||||
CloseServiceHandle(scm);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start service (ERROR_SERVICE_ALREADY_RUNNING is fine — means it's already up)
|
||||
if (!StartServiceW(svc, 0, nullptr)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err != ERROR_SERVICE_ALREADY_RUNNING) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to start driver (error %1).\n"
|
||||
"Ensure test signing is enabled and the driver is properly signed.").arg(err);
|
||||
CloseServiceHandle(svc);
|
||||
CloseServiceHandle(scm);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Done with SCM — don't hold handles open
|
||||
CloseServiceHandle(svc);
|
||||
CloseServiceHandle(scm);
|
||||
|
||||
// Open device handle
|
||||
m_driverHandle = CreateFileA(RCX_DRV_USERMODE_PATH,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (m_driverHandle == INVALID_HANDLE_VALUE) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Driver started but could not open device handle.\n"
|
||||
"Device path: %1").arg(QString::fromLatin1(RCX_DRV_USERMODE_PATH));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify with ping
|
||||
RcxDrvPingResponse ping{};
|
||||
if (!ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping))) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Driver opened but ping failed.");
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driverLoaded = true;
|
||||
return true;
|
||||
#else
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Kernel driver is only supported on Windows.");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void KernelMemoryPlugin::unloadDriver()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Close device handle only — service stays running so we can reconnect
|
||||
if (m_driverHandle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
m_driverLoaded = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void KernelMemoryPlugin::stopDriver()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
unloadDriver();
|
||||
|
||||
// Full cleanup: stop + delete the service
|
||||
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (scm) {
|
||||
SC_HANDLE svc = OpenServiceW(scm, L"RcxDrv", SERVICE_ALL_ACCESS);
|
||||
if (svc) {
|
||||
SERVICE_STATUS ss;
|
||||
ControlService(svc, SERVICE_CONTROL_STOP, &ss);
|
||||
DeleteService(svc);
|
||||
CloseServiceHandle(svc);
|
||||
}
|
||||
CloseServiceHandle(scm);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Plugin factory
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||
{
|
||||
return new KernelMemoryPlugin();
|
||||
}
|
||||
142
plugins/KernelMemory/KernelMemoryPlugin.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
#include "../../src/iplugin.h"
|
||||
#include "../../src/core.h"
|
||||
#include "rcx_drv_protocol.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Provider variants
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Kernel-mode process memory provider.
|
||||
* Reads/writes target process virtual memory via IOCTL_RCX_READ/WRITE_MEMORY.
|
||||
*/
|
||||
class KernelProcessProvider : public rcx::Provider
|
||||
{
|
||||
public:
|
||||
KernelProcessProvider(void* driverHandle, uint32_t pid, const QString& processName);
|
||||
~KernelProcessProvider() override = default;
|
||||
|
||||
bool read(uint64_t addr, void* buf, int len) const override;
|
||||
int size() const override;
|
||||
|
||||
bool write(uint64_t addr, const void* buf, int len) override;
|
||||
bool isWritable() const override { return true; }
|
||||
QString name() const override { return m_processName; }
|
||||
QString kind() const override { return QStringLiteral("KernelProcess"); }
|
||||
QString getSymbol(uint64_t addr) const override;
|
||||
uint64_t symbolToAddress(const QString& name) const override;
|
||||
|
||||
bool isLive() const override { return true; }
|
||||
uint64_t base() const override { return m_base; }
|
||||
int pointerSize() const override { return m_pointerSize; }
|
||||
QVector<rcx::MemoryRegion> enumerateRegions() const override;
|
||||
bool isReadable(uint64_t, int len) const override { return m_driverHandle && len >= 0; }
|
||||
|
||||
uint32_t pid() const { return m_pid; }
|
||||
uint64_t peb() const override { return m_peb; }
|
||||
QVector<ThreadInfo> tebs() const override;
|
||||
|
||||
// ── Paging / address translation ──
|
||||
bool hasKernelPaging() const override { return true; }
|
||||
uint64_t getCr3() const override;
|
||||
rcx::VtopResult translateAddress(uint64_t va) const override;
|
||||
QVector<uint64_t> readPageTable(uint64_t physAddr, int startIdx = 0, int count = 512) const override;
|
||||
void* driverHandle() const { return m_driverHandle; }
|
||||
|
||||
private:
|
||||
void queryPeb();
|
||||
void cacheModules();
|
||||
|
||||
void* m_driverHandle;
|
||||
uint32_t m_pid;
|
||||
QString m_processName;
|
||||
uint64_t m_base = 0;
|
||||
int m_pointerSize = 8;
|
||||
uint64_t m_peb = 0;
|
||||
mutable uint64_t m_cr3Cache = 0;
|
||||
|
||||
struct ModuleInfo {
|
||||
QString name;
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
};
|
||||
QVector<ModuleInfo> m_modules;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kernel-mode physical memory provider.
|
||||
* Reads/writes raw physical addresses via IOCTL_RCX_READ/WRITE_PHYS.
|
||||
*/
|
||||
class KernelPhysProvider : public rcx::Provider
|
||||
{
|
||||
public:
|
||||
KernelPhysProvider(void* driverHandle, uint64_t baseAddr);
|
||||
~KernelPhysProvider() override = default;
|
||||
|
||||
bool read(uint64_t addr, void* buf, int len) const override;
|
||||
int size() const override { return m_driverHandle ? 0x10000 : 0; }
|
||||
|
||||
bool write(uint64_t addr, const void* buf, int len) override;
|
||||
bool isWritable() const override { return true; }
|
||||
QString name() const override { return QStringLiteral("Physical Memory"); }
|
||||
QString kind() const override { return QStringLiteral("Physical"); }
|
||||
|
||||
bool isLive() const override { return true; }
|
||||
uint64_t base() const override { return m_baseAddr; }
|
||||
bool isReadable(uint64_t, int len) const override { return m_driverHandle && len >= 0; }
|
||||
|
||||
void setBaseAddr(uint64_t addr) { m_baseAddr = addr; }
|
||||
void* driverHandle() const { return m_driverHandle; }
|
||||
|
||||
private:
|
||||
void* m_driverHandle;
|
||||
uint64_t m_baseAddr;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Plugin
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class KernelMemoryPlugin : public IProviderPlugin
|
||||
{
|
||||
public:
|
||||
KernelMemoryPlugin();
|
||||
~KernelMemoryPlugin() override;
|
||||
|
||||
std::string Name() const override { return "Kernel Memory"; }
|
||||
std::string Version() const override { return "1.0.0"; }
|
||||
std::string Author() const override { return "Reclass"; }
|
||||
std::string Description() const override { return "Read and write memory via kernel driver (IOCTL)"; }
|
||||
k_ELoadType LoadType() const override { return k_ELoadTypeManual; }
|
||||
QIcon Icon() const override;
|
||||
|
||||
bool canHandle(const QString& target) const override;
|
||||
std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg) override;
|
||||
uint64_t getInitialBaseAddress(const QString& target) const override;
|
||||
bool selectTarget(QWidget* parent, QString* target) override;
|
||||
|
||||
bool providesProcessList() const override { return true; }
|
||||
QVector<PluginProcessInfo> enumerateProcesses() override;
|
||||
void populatePluginMenu(QMenu* menu) override;
|
||||
|
||||
private:
|
||||
bool ensureDriverLoaded(QString* errorMsg = nullptr);
|
||||
void unloadDriver(); // close handle only — service stays running
|
||||
void stopDriver(); // full cleanup: close handle + stop + delete service
|
||||
QString driverPath() const;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
#endif
|
||||
bool m_driverLoaded = false;
|
||||
};
|
||||
|
||||
// Plugin export
|
||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||
99
plugins/KernelMemory/driver/build_driver.bat
Normal file
@@ -0,0 +1,99 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: ── Auto-detect MSVC (override with MSVC env var) ──
|
||||
if not defined MSVC (
|
||||
set "VSBASE=C:\Program Files\Microsoft Visual Studio\2022"
|
||||
for %%E in (Enterprise Professional Community BuildTools) do (
|
||||
if exist "!VSBASE!\%%E\VC\Tools\MSVC" (
|
||||
for /f "delims=" %%V in ('dir /b /ad /o-n "!VSBASE!\%%E\VC\Tools\MSVC" 2^>nul') do (
|
||||
if not defined MSVC set "MSVC=!VSBASE!\%%E\VC\Tools\MSVC\%%V"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
if not defined MSVC (
|
||||
echo ERROR: Could not find MSVC toolchain
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: ── Auto-detect WDK (override with WDK_INC_ROOT and WDK_LIB_ROOT env vars) ──
|
||||
:: SDK_INC_ROOT is optional; when WDK is installed traditionally, SDK shared
|
||||
:: headers live alongside WDK headers. NuGet splits them into a separate package.
|
||||
if not defined WDK_INC_ROOT (
|
||||
set "WDK=C:\Program Files (x86)\Windows Kits\10"
|
||||
set WDKVER=
|
||||
for /f "delims=" %%V in ('dir /b /ad /o-n "!WDK!\Include" 2^>nul') do (
|
||||
if exist "!WDK!\Include\%%V\km\ntddk.h" (
|
||||
if not defined WDKVER set "WDKVER=%%V"
|
||||
)
|
||||
)
|
||||
if not defined WDKVER (
|
||||
echo ERROR: Could not find WDK headers under !WDK!\Include
|
||||
echo Set WDK_INC_ROOT and WDK_LIB_ROOT environment variables to override.
|
||||
exit /b 1
|
||||
)
|
||||
set "WDK_INC_ROOT=!WDK!\Include\!WDKVER!"
|
||||
set "WDK_LIB_ROOT=!WDK!\Lib\!WDKVER!"
|
||||
set "SDK_INC_ROOT=!WDK!\Include\!WDKVER!"
|
||||
)
|
||||
|
||||
:: If SDK_INC_ROOT not set, default to WDK_INC_ROOT (traditional install has both)
|
||||
if not defined SDK_INC_ROOT set "SDK_INC_ROOT=%WDK_INC_ROOT%"
|
||||
|
||||
echo Using MSVC: %MSVC%
|
||||
echo Using WDK inc: %WDK_INC_ROOT%
|
||||
echo Using SDK inc: %SDK_INC_ROOT%
|
||||
echo Using WDK lib: %WDK_LIB_ROOT%
|
||||
|
||||
set "CL_EXE=%MSVC%\bin\Hostx64\x64\cl.exe"
|
||||
set "LINK_EXE=%MSVC%\bin\Hostx64\x64\link.exe"
|
||||
|
||||
set "SRCDIR=%~dp0"
|
||||
set "OUTDIR=%SRCDIR%build"
|
||||
|
||||
if not exist "%OUTDIR%" mkdir "%OUTDIR%"
|
||||
|
||||
echo === Compiling rcxdrv.c ===
|
||||
"%CL_EXE%" /nologo /c /Zi /W4 /WX- /O2 /GS- ^
|
||||
/D "NDEBUG" /D "_AMD64_" /D "AMD64" /D "_WIN64" /D "KERNEL" ^
|
||||
/D "NTDDI_VERSION=0x0A000000" ^
|
||||
/I "%WDK_INC_ROOT%\km" ^
|
||||
/I "%WDK_INC_ROOT%\km\crt" ^
|
||||
/I "%WDK_INC_ROOT%\shared" ^
|
||||
/I "%SDK_INC_ROOT%\shared" ^
|
||||
/I "%SDK_INC_ROOT%\ucrt" ^
|
||||
/kernel ^
|
||||
/Fo"%OUTDIR%\rcxdrv.obj" ^
|
||||
"%SRCDIR%rcxdrv.c"
|
||||
if errorlevel 1 goto :fail
|
||||
|
||||
echo === Linking rcxdrv.sys ===
|
||||
"%LINK_EXE%" /nologo ^
|
||||
/OUT:"%OUTDIR%\rcxdrv.sys" ^
|
||||
/DRIVER:WDM ^
|
||||
/SUBSYSTEM:NATIVE ^
|
||||
/ENTRY:DriverEntry ^
|
||||
/MACHINE:X64 ^
|
||||
/NODEFAULTLIB ^
|
||||
/RELEASE ^
|
||||
/MERGE:.rdata=.text ^
|
||||
/INTEGRITYCHECK ^
|
||||
/PDBALTPATH:rcxdrv.pdb ^
|
||||
/PDB:"%OUTDIR%\rcxdrv.pdb" ^
|
||||
"%OUTDIR%\rcxdrv.obj" ^
|
||||
"%WDK_LIB_ROOT%\km\x64\ntoskrnl.lib" ^
|
||||
"%WDK_LIB_ROOT%\km\x64\hal.lib" ^
|
||||
"%WDK_LIB_ROOT%\km\x64\BufferOverflowK.lib" ^
|
||||
"%MSVC%\lib\x64\libcmt.lib"
|
||||
if errorlevel 1 goto :fail
|
||||
|
||||
echo.
|
||||
echo === SUCCESS ===
|
||||
echo Output: %OUTDIR%\rcxdrv.sys
|
||||
goto :eof
|
||||
|
||||
:fail
|
||||
echo.
|
||||
echo === BUILD FAILED ===
|
||||
exit /b 1
|
||||
808
plugins/KernelMemory/driver/rcxdrv.c
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* rcxdrv.c -- Minimal kernel-mode memory driver for Reclass.
|
||||
*
|
||||
* Provides: virtual memory R/W (per-process), physical memory R/W,
|
||||
* region/PEB/module/TEB query, CR3 read, virtual-to-physical translation.
|
||||
*
|
||||
* Safety: all inputs validated, SEH around privileged instructions,
|
||||
* MmCopyVirtualMemory for cross-process reads (no attach deadlock),
|
||||
* METHOD_BUFFERED (no raw user pointers).
|
||||
*/
|
||||
#include <ntifs.h>
|
||||
#include "../rcx_drv_protocol.h"
|
||||
|
||||
/* ── Undocumented but stable kernel exports (Vista+) ────────────── */
|
||||
|
||||
NTSTATUS NTAPI MmCopyVirtualMemory(
|
||||
PEPROCESS SourceProcess, PVOID SourceAddress,
|
||||
PEPROCESS TargetProcess, PVOID TargetAddress,
|
||||
SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode,
|
||||
PSIZE_T ReturnSize);
|
||||
|
||||
PPEB NTAPI PsGetProcessPeb(PEPROCESS Process);
|
||||
PVOID NTAPI PsGetProcessWow64Process(PEPROCESS Process);
|
||||
PVOID NTAPI PsGetThreadTeb(PETHREAD Thread);
|
||||
|
||||
/*
|
||||
* PsGetNextProcessThread is undocumented (not in any .lib).
|
||||
* We resolve it dynamically via MmGetSystemRoutineAddress.
|
||||
*/
|
||||
typedef PETHREAD (NTAPI *PsGetNextProcessThread_t)(PEPROCESS Process, PETHREAD Thread);
|
||||
static PsGetNextProcessThread_t g_PsGetNextProcessThread = NULL;
|
||||
|
||||
/* ── Manual structure definitions (kernel-mode) ─────────────────── */
|
||||
/* These are partially opaque in WDK headers; define just the offsets we need. */
|
||||
|
||||
typedef struct _MEMORY_BASIC_INFORMATION_KM {
|
||||
PVOID BaseAddress;
|
||||
PVOID AllocationBase;
|
||||
ULONG AllocationProtect;
|
||||
SIZE_T RegionSize;
|
||||
ULONG State;
|
||||
ULONG Protect;
|
||||
ULONG Type;
|
||||
} MEMORY_BASIC_INFORMATION_KM;
|
||||
|
||||
#define MEM_COMMIT_KM 0x1000
|
||||
|
||||
/* PEB.Ldr minimal definition for module enumeration */
|
||||
typedef struct _PEB_LDR_DATA_KM {
|
||||
UCHAR Reserved1[8];
|
||||
PVOID Reserved2[3];
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
} PEB_LDR_DATA_KM;
|
||||
|
||||
/* PEB minimal: only need Ldr at offset 0x18 (x64) */
|
||||
typedef struct _PEB_KM {
|
||||
UCHAR Reserved1[2];
|
||||
UCHAR BeingDebugged;
|
||||
UCHAR Reserved2[0x15];
|
||||
PEB_LDR_DATA_KM* Ldr; /* offset 0x18 on x64 */
|
||||
} PEB_KM;
|
||||
|
||||
/* LDR_DATA_TABLE_ENTRY minimal for walking InLoadOrderModuleList */
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY_KM {
|
||||
LIST_ENTRY InLoadOrderLinks; /* offset 0x00 */
|
||||
LIST_ENTRY InMemoryOrderLinks; /* offset 0x10 */
|
||||
LIST_ENTRY InInitializationOrderLinks; /* offset 0x20 */
|
||||
PVOID DllBase; /* offset 0x30 */
|
||||
PVOID EntryPoint; /* offset 0x38 */
|
||||
ULONG SizeOfImage; /* offset 0x40 */
|
||||
ULONG _pad;
|
||||
UNICODE_STRING FullDllName; /* offset 0x48 */
|
||||
UNICODE_STRING BaseDllName; /* offset 0x58 */
|
||||
} LDR_DATA_TABLE_ENTRY_KM;
|
||||
|
||||
/* ── Forward declarations ────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS DispatchCreateClose(PDEVICE_OBJECT dev, PIRP irp);
|
||||
static NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp);
|
||||
DRIVER_UNLOAD DriverUnload;
|
||||
|
||||
/* ZwCurrentProcess() macro for ZwQueryVirtualMemory */
|
||||
#ifndef ZwCurrentProcess
|
||||
#define ZwCurrentProcess() ((HANDLE)(LONG_PTR)-1)
|
||||
#endif
|
||||
|
||||
/* ── Helpers ─────────────────────────────────────────────────────── */
|
||||
|
||||
#define VALIDATE_INPUT(irp, stk, T) \
|
||||
do { \
|
||||
if ((stk)->Parameters.DeviceIoControl.InputBufferLength < sizeof(T)) { \
|
||||
(irp)->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; \
|
||||
(irp)->IoStatus.Information = 0; \
|
||||
IoCompleteRequest((irp), IO_NO_INCREMENT); \
|
||||
return STATUS_BUFFER_TOO_SMALL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define VALIDATE_OUTPUT(irp, stk, minSize) \
|
||||
do { \
|
||||
if ((stk)->Parameters.DeviceIoControl.OutputBufferLength < (ULONG)(minSize)) { \
|
||||
(irp)->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; \
|
||||
(irp)->IoStatus.Information = 0; \
|
||||
IoCompleteRequest((irp), IO_NO_INCREMENT); \
|
||||
return STATUS_BUFFER_TOO_SMALL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static NTSTATUS LookupProcess(ULONG pid, PEPROCESS* proc)
|
||||
{
|
||||
return PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)pid, proc);
|
||||
}
|
||||
|
||||
/* ── Safe physical mapping (MDL-based, avoids MmMapIoSpace BSOD) ── */
|
||||
/*
|
||||
* MmMapIoSpace/MmUnmapIoSpace BSODs (bugcheck 0x50 in
|
||||
* MiClearMappingAndDereferenceIoSpace) when used on RAM-backed physical
|
||||
* addresses. MDL-based mapping is safe for both RAM and MMIO.
|
||||
*
|
||||
* CRITICAL: cacheType must match the existing kernel mapping of the page.
|
||||
* Use MmCached for RAM pages (already mapped cached by the kernel).
|
||||
* Use MmNonCached ONLY for MMIO/device registers.
|
||||
* Mismatched cache attributes (e.g. MmNonCached on RAM) cause silent
|
||||
* kernel memory corruption via CPU cache coherency conflicts.
|
||||
*/
|
||||
|
||||
typedef struct { PMDL mdl; PVOID base; } PHYS_MAP_CTX;
|
||||
|
||||
static PVOID MapPhysical(uint64_t physAddr, SIZE_T size,
|
||||
MEMORY_CACHING_TYPE cacheType, PHYS_MAP_CTX* ctx)
|
||||
{
|
||||
ctx->mdl = NULL;
|
||||
ctx->base = NULL;
|
||||
|
||||
ULONG_PTR pageOff = (ULONG_PTR)(physAddr & (PAGE_SIZE - 1));
|
||||
SIZE_T totalSize = pageOff + size;
|
||||
ULONG pages = (ULONG)((totalSize + PAGE_SIZE - 1) / PAGE_SIZE);
|
||||
|
||||
PMDL mdl = IoAllocateMdl(NULL, (ULONG)totalSize, FALSE, FALSE, NULL);
|
||||
if (!mdl) return NULL;
|
||||
|
||||
PPFN_NUMBER pfn = MmGetMdlPfnArray(mdl);
|
||||
PFN_NUMBER startPfn = (PFN_NUMBER)(physAddr / PAGE_SIZE);
|
||||
for (ULONG i = 0; i < pages; i++)
|
||||
pfn[i] = startPfn + i;
|
||||
mdl->MdlFlags |= MDL_PAGES_LOCKED;
|
||||
|
||||
__try {
|
||||
ctx->base = MmMapLockedPagesSpecifyCache(
|
||||
mdl, KernelMode, cacheType, NULL, FALSE, NormalPagePriority);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
IoFreeMdl(mdl);
|
||||
return NULL;
|
||||
}
|
||||
if (!ctx->base) { IoFreeMdl(mdl); return NULL; }
|
||||
|
||||
ctx->mdl = mdl;
|
||||
return (PUCHAR)ctx->base + pageOff;
|
||||
}
|
||||
|
||||
static void UnmapPhysical(PHYS_MAP_CTX* ctx)
|
||||
{
|
||||
if (ctx->base) MmUnmapLockedPages(ctx->base, ctx->mdl);
|
||||
if (ctx->mdl) IoFreeMdl(ctx->mdl);
|
||||
ctx->base = NULL;
|
||||
ctx->mdl = NULL;
|
||||
}
|
||||
|
||||
/* ── Virtual memory read ─────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleReadMemory(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvReadRequest);
|
||||
|
||||
struct RcxDrvReadRequest* req = (struct RcxDrvReadRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_VIRTUAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
|
||||
VALIDATE_OUTPUT(irp, stk, req->length);
|
||||
|
||||
/* Save request fields before MmCopyVirtualMemory overwrites SystemBuffer.
|
||||
* METHOD_BUFFERED aliases input and output to the same buffer, so the
|
||||
* copy destination (SystemBuffer) clobbers req->* fields. */
|
||||
ULONG pid = req->pid;
|
||||
uint64_t address = req->address;
|
||||
ULONG length = req->length;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
SIZE_T bytesRead = 0;
|
||||
st = MmCopyVirtualMemory(
|
||||
proc, (PVOID)address,
|
||||
PsGetCurrentProcess(), irp->AssociatedIrp.SystemBuffer,
|
||||
(SIZE_T)length, KernelMode, &bytesRead);
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
/* Partial reads: zero remainder, report success */
|
||||
if (st == STATUS_PARTIAL_COPY) {
|
||||
RtlZeroMemory((PUCHAR)irp->AssociatedIrp.SystemBuffer + bytesRead,
|
||||
length - bytesRead);
|
||||
irp->IoStatus.Information = length;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
irp->IoStatus.Information = NT_SUCCESS(st) ? length : 0;
|
||||
return st;
|
||||
}
|
||||
|
||||
/* ── Virtual memory write ────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleWriteMemory(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
ULONG inputLen = stk->Parameters.DeviceIoControl.InputBufferLength;
|
||||
if (inputLen < sizeof(struct RcxDrvWriteRequest))
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
struct RcxDrvWriteRequest* req = (struct RcxDrvWriteRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_VIRTUAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (inputLen < sizeof(struct RcxDrvWriteRequest) + req->length)
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
PUCHAR data = (PUCHAR)req + sizeof(struct RcxDrvWriteRequest);
|
||||
SIZE_T bytesWritten = 0;
|
||||
st = MmCopyVirtualMemory(
|
||||
PsGetCurrentProcess(), data,
|
||||
proc, (PVOID)req->address,
|
||||
(SIZE_T)req->length, KernelMode, &bytesWritten);
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
irp->IoStatus.Information = 0;
|
||||
return st;
|
||||
}
|
||||
|
||||
/* ── Physical memory read ────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleReadPhys(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvPhysReadRequest);
|
||||
|
||||
struct RcxDrvPhysReadRequest* req = (struct RcxDrvPhysReadRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_PHYSICAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (req->width != 0 && req->width != 1 && req->width != 2 && req->width != 4)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
|
||||
VALIDATE_OUTPUT(irp, stk, req->length);
|
||||
|
||||
/* Save request fields before SystemBuffer is overwritten (METHOD_BUFFERED
|
||||
* aliases input and output to the same buffer). */
|
||||
uint64_t physAddress = req->physAddress;
|
||||
ULONG length = req->length;
|
||||
ULONG width = req->width;
|
||||
|
||||
PUCHAR dst = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
if (width == 0) {
|
||||
/* Byte copy -- use MmCopyMemory (safe for both RAM and MMIO) */
|
||||
MM_COPY_ADDRESS srcAddr;
|
||||
srcAddr.PhysicalAddress.QuadPart = (LONGLONG)physAddress;
|
||||
SIZE_T bytesCopied = 0;
|
||||
NTSTATUS st = MmCopyMemory(dst, srcAddr, (SIZE_T)length,
|
||||
MM_COPY_MEMORY_PHYSICAL, &bytesCopied);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
if (bytesCopied < length)
|
||||
RtlZeroMemory(dst + bytesCopied, length - bytesCopied);
|
||||
irp->IoStatus.Information = length;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* Width-aware MMIO reads -- map via MDL (safe for all physical addresses).
|
||||
* Use MmNonCached: width>0 implies MMIO register access where uncached
|
||||
* semantics are required for correct device interaction. */
|
||||
PHYS_MAP_CTX mapCtx;
|
||||
PUCHAR src = (PUCHAR)MapPhysical(physAddress, (SIZE_T)length, MmNonCached, &mapCtx);
|
||||
if (!src) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
__try {
|
||||
ULONG off = 0;
|
||||
while (off + width <= length) {
|
||||
if (width == 1)
|
||||
dst[off] = READ_REGISTER_UCHAR(&src[off]);
|
||||
else if (width == 2)
|
||||
*(USHORT*)(dst + off) = READ_REGISTER_USHORT((PUSHORT)(src + off));
|
||||
else
|
||||
*(ULONG*)(dst + off) = READ_REGISTER_ULONG((PULONG)(src + off));
|
||||
off += width;
|
||||
}
|
||||
if (off < length)
|
||||
RtlZeroMemory(dst + off, length - off);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
UnmapPhysical(&mapCtx);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
UnmapPhysical(&mapCtx);
|
||||
irp->IoStatus.Information = length;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Physical memory write ───────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleWritePhys(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
ULONG inputLen = stk->Parameters.DeviceIoControl.InputBufferLength;
|
||||
if (inputLen < sizeof(struct RcxDrvPhysWriteRequest))
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
struct RcxDrvPhysWriteRequest* req = (struct RcxDrvPhysWriteRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_PHYSICAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (req->width != 0 && req->width != 1 && req->width != 2 && req->width != 4)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (inputLen < sizeof(struct RcxDrvPhysWriteRequest) + req->length)
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PUCHAR src = (PUCHAR)req + sizeof(struct RcxDrvPhysWriteRequest);
|
||||
|
||||
/* Map via MDL (safe for both RAM and MMIO).
|
||||
* width==0 → RAM byte write (MmCached to avoid cache attribute conflict).
|
||||
* width>0 → MMIO register write (MmNonCached for correct device semantics). */
|
||||
MEMORY_CACHING_TYPE ct = (req->width == 0) ? MmCached : MmNonCached;
|
||||
PHYS_MAP_CTX mapCtx;
|
||||
PUCHAR dst = (PUCHAR)MapPhysical(req->physAddress, (SIZE_T)req->length, ct, &mapCtx);
|
||||
if (!dst) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
__try {
|
||||
if (req->width == 0) {
|
||||
RtlCopyMemory(dst, src, req->length);
|
||||
} else {
|
||||
ULONG off = 0;
|
||||
while (off + req->width <= req->length) {
|
||||
if (req->width == 1)
|
||||
WRITE_REGISTER_UCHAR(&dst[off], src[off]);
|
||||
else if (req->width == 2)
|
||||
WRITE_REGISTER_USHORT((PUSHORT)(dst + off), *(USHORT*)(src + off));
|
||||
else
|
||||
WRITE_REGISTER_ULONG((PULONG)(dst + off), *(ULONG*)(src + off));
|
||||
off += req->width;
|
||||
}
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
UnmapPhysical(&mapCtx);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
UnmapPhysical(&mapCtx);
|
||||
irp->IoStatus.Information = 0;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Ping ────────────────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandlePing(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvPingResponse));
|
||||
|
||||
struct RcxDrvPingResponse* rsp = (struct RcxDrvPingResponse*)irp->AssociatedIrp.SystemBuffer;
|
||||
rsp->version = RCX_DRV_VERSION;
|
||||
rsp->driverBuild = __LINE__;
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvPingResponse);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query PEB ───────────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleQueryPeb(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryPebRequest);
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvQueryPebResponse));
|
||||
|
||||
struct RcxDrvQueryPebRequest* req = (struct RcxDrvQueryPebRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
struct RcxDrvQueryPebResponse* rsp = (struct RcxDrvQueryPebResponse*)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
rsp->pebAddress = (uint64_t)(ULONG_PTR)PsGetProcessPeb(proc);
|
||||
rsp->pointerSize = 8;
|
||||
rsp->_pad = 0;
|
||||
|
||||
/* Detect WoW64 (32-bit process on 64-bit OS) */
|
||||
PVOID wow64 = PsGetProcessWow64Process(proc);
|
||||
if (wow64) {
|
||||
rsp->pebAddress = (uint64_t)(ULONG_PTR)wow64;
|
||||
rsp->pointerSize = 4;
|
||||
}
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvQueryPebResponse);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query Regions ───────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleQueryRegions(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryRegionsRequest);
|
||||
|
||||
struct RcxDrvQueryRegionsRequest* req = (struct RcxDrvQueryRegionsRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
ULONG maxEntries = outputLen / sizeof(struct RcxDrvRegionEntry);
|
||||
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
/* Attach to target process to query its address space.
|
||||
* IOCTLs arrive at PASSIVE_LEVEL; KeStackAttachProcess requires <= APC_LEVEL.
|
||||
* ZwQueryVirtualMemory with ZwCurrentProcess() while attached queries the
|
||||
* attached process's address space (correct). */
|
||||
KAPC_STATE apcState;
|
||||
KeStackAttachProcess(proc, &apcState);
|
||||
|
||||
struct RcxDrvRegionEntry* entries = (struct RcxDrvRegionEntry*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG count = 0;
|
||||
PVOID addr = NULL;
|
||||
MEMORY_BASIC_INFORMATION_KM mbi;
|
||||
|
||||
while (count < maxEntries) {
|
||||
SIZE_T retLen = 0;
|
||||
st = ZwQueryVirtualMemory(ZwCurrentProcess(), addr, 0 /*MemoryBasicInformation*/,
|
||||
&mbi, sizeof(mbi), &retLen);
|
||||
if (!NT_SUCCESS(st)) break;
|
||||
|
||||
if (mbi.State == MEM_COMMIT_KM) {
|
||||
entries[count].base = (uint64_t)(ULONG_PTR)mbi.BaseAddress;
|
||||
entries[count].size = (uint64_t)mbi.RegionSize;
|
||||
entries[count].protect = mbi.Protect;
|
||||
entries[count].state = mbi.State;
|
||||
count++;
|
||||
}
|
||||
|
||||
ULONG_PTR next = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
|
||||
if (next <= (ULONG_PTR)addr) break; /* overflow */
|
||||
addr = (PVOID)next;
|
||||
}
|
||||
|
||||
KeUnstackDetachProcess(&apcState);
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
irp->IoStatus.Information = count * sizeof(struct RcxDrvRegionEntry);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query Modules ───────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleQueryModules(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryModulesRequest);
|
||||
|
||||
struct RcxDrvQueryModulesRequest* req = (struct RcxDrvQueryModulesRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
ULONG maxEntries = outputLen / sizeof(struct RcxDrvModuleEntry);
|
||||
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
/* Attach to target process to read PEB->Ldr */
|
||||
KAPC_STATE apcState;
|
||||
KeStackAttachProcess(proc, &apcState);
|
||||
|
||||
struct RcxDrvModuleEntry* entries = (struct RcxDrvModuleEntry*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG count = 0;
|
||||
|
||||
__try {
|
||||
/* Read PEB address */
|
||||
PEB_KM* peb = (PEB_KM*)PsGetProcessPeb(proc);
|
||||
if (!peb) goto done;
|
||||
ProbeForRead(peb, sizeof(PEB_KM), 1);
|
||||
|
||||
/* PEB->Ldr at offset 0x18 (x64) */
|
||||
PEB_LDR_DATA_KM* ldr = peb->Ldr;
|
||||
if (!ldr) goto done;
|
||||
ProbeForRead(ldr, sizeof(PEB_LDR_DATA_KM), 1);
|
||||
|
||||
/* Walk InLoadOrderModuleList */
|
||||
LIST_ENTRY* head = &ldr->InLoadOrderModuleList;
|
||||
LIST_ENTRY* cur = head->Flink;
|
||||
|
||||
while (cur != head && count < maxEntries) {
|
||||
LDR_DATA_TABLE_ENTRY_KM* entry = CONTAINING_RECORD(cur, LDR_DATA_TABLE_ENTRY_KM, InLoadOrderLinks);
|
||||
|
||||
entries[count].base = (uint64_t)(ULONG_PTR)entry->DllBase;
|
||||
entries[count].size = (uint64_t)entry->SizeOfImage;
|
||||
|
||||
/* Copy wide-char name (truncate to 259 chars + null) */
|
||||
USHORT nameLen = entry->BaseDllName.Length / sizeof(WCHAR);
|
||||
if (nameLen > 259) nameLen = 259;
|
||||
if (entry->BaseDllName.Buffer) {
|
||||
RtlCopyMemory(entries[count].name, entry->BaseDllName.Buffer,
|
||||
nameLen * sizeof(uint16_t));
|
||||
}
|
||||
entries[count].name[nameLen] = 0;
|
||||
|
||||
count++;
|
||||
cur = cur->Flink;
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
/* Partial results are fine */
|
||||
}
|
||||
|
||||
done:
|
||||
KeUnstackDetachProcess(&apcState);
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
irp->IoStatus.Information = count * sizeof(struct RcxDrvModuleEntry);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query TEBs ──────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Walk the target process's thread list to collect TEB addresses.
|
||||
* Uses PsGetNextProcessThread (undocumented but stable since Vista).
|
||||
*/
|
||||
static NTSTATUS HandleQueryTebs(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryTebsRequest);
|
||||
|
||||
struct RcxDrvQueryTebsRequest* req = (struct RcxDrvQueryTebsRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
ULONG maxEntries = outputLen / sizeof(struct RcxDrvTebEntry);
|
||||
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
struct RcxDrvTebEntry* entries = (struct RcxDrvTebEntry*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG count = 0;
|
||||
|
||||
if (!g_PsGetNextProcessThread) {
|
||||
ObDereferenceObject(proc);
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* PsGetNextProcessThread increments the ref on the returned PETHREAD and
|
||||
* dereferences the previous one. We must release the last thread if we
|
||||
* exit the loop early (exception or maxEntries hit). */
|
||||
{
|
||||
PETHREAD thread = NULL;
|
||||
__try {
|
||||
while ((thread = g_PsGetNextProcessThread(proc, thread)) != NULL) {
|
||||
if (count >= maxEntries) {
|
||||
/* Hit limit — release the thread PsGetNextProcessThread just returned */
|
||||
ObDereferenceObject(thread);
|
||||
break;
|
||||
}
|
||||
PVOID teb = PsGetThreadTeb(thread);
|
||||
if (teb) {
|
||||
entries[count].tebAddress = (uint64_t)(ULONG_PTR)teb;
|
||||
entries[count].threadId = (uint32_t)(ULONG_PTR)PsGetThreadId(thread);
|
||||
entries[count]._pad = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
/* Exception mid-iteration: thread holds a referenced PETHREAD — release it */
|
||||
if (thread)
|
||||
ObDereferenceObject(thread);
|
||||
}
|
||||
}
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
irp->IoStatus.Information = count * sizeof(struct RcxDrvTebEntry);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Read CR3 (DirectoryTableBase) ────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* EPROCESS.DirectoryTableBase offset. Stable across Win10/11 x64.
|
||||
* Verified: 0x028 on 1507-22H2+ (KPROCESS is at offset 0 of EPROCESS).
|
||||
*/
|
||||
#define KPROCESS_DIRECTORY_TABLE_BASE 0x028
|
||||
|
||||
static NTSTATUS HandleReadCr3(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvReadCr3Request);
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvReadCr3Response));
|
||||
|
||||
struct RcxDrvReadCr3Request* req = (struct RcxDrvReadCr3Request*)irp->AssociatedIrp.SystemBuffer;
|
||||
struct RcxDrvReadCr3Response* rsp = (struct RcxDrvReadCr3Response*)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
__try {
|
||||
rsp->cr3 = *(uint64_t*)((PUCHAR)proc + KPROCESS_DIRECTORY_TABLE_BASE);
|
||||
/* Mask off PCID bits (bits 0-11) to get the PML4 physical address */
|
||||
rsp->cr3 &= ~0xFFFULL;
|
||||
rsp->kernelCr3 = rsp->cr3; /* same on non-KPTI; KPTI shadow is not easily accessible */
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
ObDereferenceObject(proc);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvReadCr3Response);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Virtual-to-Physical address translation ─────────────────────── */
|
||||
|
||||
/* NOTE: This walks the page table non-atomically via 4 sequential physical reads.
|
||||
* The page table can be modified between reads (e.g., page-out, remap). This is
|
||||
* an inherent limitation shared by WinDbg's !vtop and similar tools. For a
|
||||
* debugging/reversing tool this tradeoff is acceptable. */
|
||||
|
||||
/* Extract physical frame address from a page table entry (bits 51:12) */
|
||||
#define PTE_FRAME(pte) ((pte) & 0x000FFFFFFFFFF000ULL)
|
||||
/* Check Present bit (bit 0) */
|
||||
#define PTE_PRESENT(pte) ((pte) & 1ULL)
|
||||
/* Check Page Size bit (bit 7) -- indicates large/huge page */
|
||||
#define PTE_PS(pte) ((pte) & (1ULL << 7))
|
||||
|
||||
static NTSTATUS HandleVtop(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvVtopRequest);
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvVtopResponse));
|
||||
|
||||
struct RcxDrvVtopRequest* req = (struct RcxDrvVtopRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
struct RcxDrvVtopResponse* rsp = (struct RcxDrvVtopResponse*)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
/* Read CR3 */
|
||||
uint64_t cr3;
|
||||
__try {
|
||||
cr3 = *(uint64_t*)((PUCHAR)proc + KPROCESS_DIRECTORY_TABLE_BASE);
|
||||
cr3 &= ~0xFFFULL;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
ObDereferenceObject(proc);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
uint64_t va = req->virtualAddress;
|
||||
RtlZeroMemory(rsp, sizeof(*rsp));
|
||||
|
||||
/* Extract indices from virtual address:
|
||||
* [47:39] = PML4 index, [38:30] = PDPT index,
|
||||
* [29:21] = PD index, [20:12] = PT index,
|
||||
* [11:0] = page offset */
|
||||
ULONG pml4Idx = (ULONG)((va >> 39) & 0x1FF);
|
||||
ULONG pdptIdx = (ULONG)((va >> 30) & 0x1FF);
|
||||
ULONG pdIdx = (ULONG)((va >> 21) & 0x1FF);
|
||||
ULONG ptIdx = (ULONG)((va >> 12) & 0x1FF);
|
||||
|
||||
MM_COPY_ADDRESS ca;
|
||||
SIZE_T copied;
|
||||
uint64_t entry;
|
||||
|
||||
/* Level 4: PML4 -- use MmCopyMemory (safe for RAM, unlike MmMapIoSpace) */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(cr3 + pml4Idx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pml4e = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
|
||||
/* Level 3: PDPT */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + pdptIdx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pdpte = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
if (PTE_PS(entry)) {
|
||||
/* 1GB huge page: physical = frame[51:30] | va[29:0] */
|
||||
rsp->physicalAddress = (entry & 0x000FFFFFC0000000ULL) | (va & 0x3FFFFFFFULL);
|
||||
rsp->pageSize = 2;
|
||||
rsp->valid = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Level 2: PD */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + pdIdx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pde = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
if (PTE_PS(entry)) {
|
||||
/* 2MB large page: physical = frame[51:21] | va[20:0] */
|
||||
rsp->physicalAddress = (entry & 0x000FFFFFFFE00000ULL) | (va & 0x1FFFFFULL);
|
||||
rsp->pageSize = 1;
|
||||
rsp->valid = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Level 1: PT */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + ptIdx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pte = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
|
||||
/* 4KB page: physical = frame[51:12] | va[11:0] */
|
||||
rsp->physicalAddress = PTE_FRAME(entry) | (va & 0xFFFULL);
|
||||
rsp->pageSize = 0;
|
||||
rsp->valid = 1;
|
||||
|
||||
done:
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvVtopResponse);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── IOCTL dispatch ──────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(dev);
|
||||
|
||||
PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(irp);
|
||||
NTSTATUS st;
|
||||
|
||||
switch (stk->Parameters.DeviceIoControl.IoControlCode) {
|
||||
case IOCTL_RCX_READ_MEMORY: st = HandleReadMemory(irp, stk); break;
|
||||
case IOCTL_RCX_WRITE_MEMORY: st = HandleWriteMemory(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_REGIONS: st = HandleQueryRegions(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_PEB: st = HandleQueryPeb(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_MODULES: st = HandleQueryModules(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_TEBS: st = HandleQueryTebs(irp, stk); break;
|
||||
case IOCTL_RCX_PING: st = HandlePing(irp, stk); break;
|
||||
case IOCTL_RCX_READ_PHYS: st = HandleReadPhys(irp, stk); break;
|
||||
case IOCTL_RCX_WRITE_PHYS: st = HandleWritePhys(irp, stk); break;
|
||||
case IOCTL_RCX_READ_CR3: st = HandleReadCr3(irp, stk); break;
|
||||
case IOCTL_RCX_VTOP: st = HandleVtop(irp, stk); break;
|
||||
default:
|
||||
st = STATUS_INVALID_DEVICE_REQUEST;
|
||||
irp->IoStatus.Information = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
irp->IoStatus.Status = st;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
return st;
|
||||
}
|
||||
|
||||
/* ── Create / Close (permit open/close) ──────────────────────────── */
|
||||
|
||||
static NTSTATUS DispatchCreateClose(PDEVICE_OBJECT dev, PIRP irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(dev);
|
||||
irp->IoStatus.Status = STATUS_SUCCESS;
|
||||
irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Unload ──────────────────────────────────────────────────────── */
|
||||
|
||||
void DriverUnload(PDRIVER_OBJECT drv)
|
||||
{
|
||||
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\DosDevices\\RcxDrv");
|
||||
IoDeleteSymbolicLink(&symlink);
|
||||
if (drv->DeviceObject)
|
||||
IoDeleteDevice(drv->DeviceObject);
|
||||
}
|
||||
|
||||
/* ── Entry point ─────────────────────────────────────────────────── */
|
||||
|
||||
NTSTATUS DriverEntry(PDRIVER_OBJECT drv, PUNICODE_STRING regPath)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(regPath);
|
||||
|
||||
/* Resolve undocumented APIs */
|
||||
UNICODE_STRING fnName = RTL_CONSTANT_STRING(L"PsGetNextProcessThread");
|
||||
g_PsGetNextProcessThread = (PsGetNextProcessThread_t)MmGetSystemRoutineAddress(&fnName);
|
||||
|
||||
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\RcxDrv");
|
||||
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\DosDevices\\RcxDrv");
|
||||
|
||||
PDEVICE_OBJECT devObj = NULL;
|
||||
NTSTATUS st = IoCreateDevice(drv, 0, &devName, FILE_DEVICE_UNKNOWN,
|
||||
FILE_DEVICE_SECURE_OPEN, FALSE, &devObj);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
st = IoCreateSymbolicLink(&symlink, &devName);
|
||||
if (!NT_SUCCESS(st)) {
|
||||
IoDeleteDevice(devObj);
|
||||
return st;
|
||||
}
|
||||
|
||||
drv->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
|
||||
drv->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
|
||||
drv->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
|
||||
drv->DriverUnload = DriverUnload;
|
||||
|
||||
devObj->Flags |= DO_BUFFERED_IO;
|
||||
devObj->Flags &= ~DO_DEVICE_INITIALIZING;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
17
plugins/KernelMemory/linux/Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
obj-m += rcxkm.o
|
||||
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) clean
|
||||
|
||||
install:
|
||||
insmod rcxkm.ko
|
||||
|
||||
uninstall:
|
||||
rmmod rcxkm
|
||||
|
||||
.PHONY: all clean install uninstall
|
||||
132
plugins/KernelMemory/linux/rcxkm.c
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* rcxkm.c -- Linux kernel module stub for Reclass kernel memory provider.
|
||||
*
|
||||
* Provides /dev/rcxkm char device with ioctl() dispatch using the same
|
||||
* protocol structs as the Windows driver (rcx_drv_protocol.h).
|
||||
*
|
||||
* Build: make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
|
||||
*
|
||||
* TODO: implement handlers (currently returns -ENOSYS for all IOCTLs).
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/pid.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
#include "../rcx_drv_protocol.h"
|
||||
|
||||
#define DEVICE_NAME "rcxkm"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Reclass");
|
||||
MODULE_DESCRIPTION("Reclass kernel memory provider (stub)");
|
||||
|
||||
/* ── IOCTL dispatch ─────────────────────────────────────────────────── */
|
||||
|
||||
static long rcxkm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
(void)filp;
|
||||
(void)arg;
|
||||
|
||||
switch (cmd) {
|
||||
case IOCTL_RCX_READ_MEMORY:
|
||||
/* TODO: find_get_pid(pid) -> get_task_struct -> access_process_vm() */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_WRITE_MEMORY:
|
||||
/* TODO: access_process_vm() with FOLL_WRITE */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_REGIONS:
|
||||
/* TODO: walk target mm->mmap via VMA iteration */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_PEB:
|
||||
/* N/A on Linux (no PEB); could return mm->start_brk or similar */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_MODULES:
|
||||
/* TODO: walk target /proc/pid/maps or mm VMAs */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_TEBS:
|
||||
/* N/A on Linux (no TEB) */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_PING: {
|
||||
struct RcxDrvPingResponse resp = {
|
||||
.version = RCX_DRV_VERSION,
|
||||
.driverBuild = 1,
|
||||
};
|
||||
if (copy_to_user((void __user *)arg, &resp, sizeof(resp)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case IOCTL_RCX_READ_PHYS:
|
||||
/* TODO: ioremap() + memcpy_fromio() */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_WRITE_PHYS:
|
||||
/* TODO: ioremap() + memcpy_toio() */
|
||||
return -ENOSYS;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── File operations ────────────────────────────────────────────────── */
|
||||
|
||||
static int rcxkm_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
(void)inode; (void)filp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcxkm_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
(void)inode; (void)filp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations rcxkm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = rcxkm_ioctl,
|
||||
.open = rcxkm_open,
|
||||
.release = rcxkm_release,
|
||||
};
|
||||
|
||||
static struct miscdevice rcxkm_device = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = DEVICE_NAME,
|
||||
.fops = &rcxkm_fops,
|
||||
};
|
||||
|
||||
/* ── Module init/exit ───────────────────────────────────────────────── */
|
||||
|
||||
static int __init rcxkm_init(void)
|
||||
{
|
||||
int ret = misc_register(&rcxkm_device);
|
||||
if (ret) {
|
||||
pr_err("rcxkm: failed to register misc device (err=%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
pr_info("rcxkm: loaded, device /dev/%s\n", DEVICE_NAME);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rcxkm_exit(void)
|
||||
{
|
||||
misc_deregister(&rcxkm_device);
|
||||
pr_info("rcxkm: unloaded\n");
|
||||
}
|
||||
|
||||
module_init(rcxkm_init);
|
||||
module_exit(rcxkm_exit);
|
||||
189
plugins/KernelMemory/rcx_drv_protocol.h
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* RCX Driver Protocol -- shared between kernel driver and usermode plugin.
|
||||
* No dependencies beyond standard C headers. Pure C, no Windows types.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef KERNEL
|
||||
/* Kernel mode build: avoid stdint.h (not in WDK km/crt) */
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
typedef signed __int64 int64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* ── Device / service names ───────────────────────────────────────── */
|
||||
#define RCX_DRV_DEVICE_NAME L"\\Device\\RcxDrv"
|
||||
#define RCX_DRV_SYMLINK_NAME L"\\DosDevices\\RcxDrv"
|
||||
#define RCX_DRV_USERMODE_PATH "\\\\.\\RcxDrv"
|
||||
#define RCX_DRV_SERVICE_NAME "RcxDrv"
|
||||
|
||||
/* ── Protocol version ─────────────────────────────────────────────── */
|
||||
#define RCX_DRV_VERSION 1
|
||||
|
||||
/* ── Size limits ──────────────────────────────────────────────────── */
|
||||
#define RCX_DRV_MAX_VIRTUAL (1024 * 1024) /* 1 MB per virtual read/write */
|
||||
#define RCX_DRV_MAX_PHYSICAL 4096 /* 4 KB per physical read/write */
|
||||
|
||||
/* ── IOCTL codes ──────────────────────────────────────────────────── */
|
||||
/* CTL_CODE(FILE_DEVICE_UNKNOWN=0x22, function, METHOD_BUFFERED=0, FILE_ANY_ACCESS=0) */
|
||||
|
||||
/* Virtual memory (per-process) */
|
||||
#define IOCTL_RCX_READ_MEMORY 0x222000 /* function 0x800 */
|
||||
#define IOCTL_RCX_WRITE_MEMORY 0x222004 /* function 0x801 */
|
||||
#define IOCTL_RCX_QUERY_REGIONS 0x222008 /* function 0x802 */
|
||||
#define IOCTL_RCX_QUERY_PEB 0x22200C /* function 0x803 */
|
||||
#define IOCTL_RCX_QUERY_MODULES 0x222010 /* function 0x804 */
|
||||
#define IOCTL_RCX_QUERY_TEBS 0x222014 /* function 0x805 */
|
||||
#define IOCTL_RCX_PING 0x222018 /* function 0x806 */
|
||||
|
||||
/* Physical memory (MMIO) */
|
||||
#define IOCTL_RCX_READ_PHYS 0x22201C /* function 0x807 */
|
||||
#define IOCTL_RCX_WRITE_PHYS 0x222020 /* function 0x808 */
|
||||
|
||||
/* Paging / address translation */
|
||||
#define IOCTL_RCX_READ_CR3 0x222044 /* function 0x811 */
|
||||
#define IOCTL_RCX_VTOP 0x222048 /* function 0x812 */
|
||||
|
||||
/* ── Request / Response structures ────────────────────────────────── */
|
||||
/* All structs are naturally aligned. Padding fields are explicit. */
|
||||
|
||||
/* -- Virtual memory -- */
|
||||
|
||||
struct RcxDrvReadRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad0;
|
||||
uint64_t address;
|
||||
uint32_t length; /* max RCX_DRV_MAX_VIRTUAL */
|
||||
uint32_t _pad1;
|
||||
};
|
||||
|
||||
/* Write: input = header + inline data bytes */
|
||||
struct RcxDrvWriteRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad0;
|
||||
uint64_t address;
|
||||
uint32_t length; /* max RCX_DRV_MAX_VIRTUAL */
|
||||
uint32_t _pad1;
|
||||
/* uint8_t data[length] follows */
|
||||
};
|
||||
|
||||
/* -- Region enumeration -- */
|
||||
|
||||
struct RcxDrvQueryRegionsRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvRegionEntry {
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
uint32_t protect; /* raw PAGE_* flags */
|
||||
uint32_t state; /* MEM_COMMIT etc. */
|
||||
};
|
||||
|
||||
/* -- PEB -- */
|
||||
|
||||
struct RcxDrvQueryPebRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvQueryPebResponse {
|
||||
uint64_t pebAddress;
|
||||
uint32_t pointerSize; /* 4 or 8 */
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
/* -- Modules -- */
|
||||
|
||||
struct RcxDrvQueryModulesRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvModuleEntry {
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
uint16_t name[260]; /* wide-char, null-terminated */
|
||||
};
|
||||
|
||||
/* -- TEBs -- */
|
||||
|
||||
struct RcxDrvQueryTebsRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvTebEntry {
|
||||
uint64_t tebAddress;
|
||||
uint32_t threadId;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
/* -- Ping -- */
|
||||
|
||||
struct RcxDrvPingResponse {
|
||||
uint32_t version;
|
||||
uint32_t driverBuild;
|
||||
};
|
||||
|
||||
/* -- Physical memory -- */
|
||||
|
||||
struct RcxDrvPhysReadRequest {
|
||||
uint64_t physAddress;
|
||||
uint32_t length; /* max RCX_DRV_MAX_PHYSICAL */
|
||||
uint32_t width; /* access width: 1, 2, or 4 (0 = memcpy) */
|
||||
};
|
||||
|
||||
struct RcxDrvPhysWriteRequest {
|
||||
uint64_t physAddress;
|
||||
uint32_t length; /* max RCX_DRV_MAX_PHYSICAL */
|
||||
uint32_t width; /* access width: 1, 2, or 4 (0 = memcpy) */
|
||||
/* uint8_t data[length] follows */
|
||||
};
|
||||
|
||||
/* -- Paging / address translation -- */
|
||||
|
||||
struct RcxDrvReadCr3Request {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvReadCr3Response {
|
||||
uint64_t cr3; /* DirectoryTableBase (PML4 physical address) */
|
||||
uint64_t kernelCr3; /* KernelDirectoryTableBase (KPTI shadow) */
|
||||
};
|
||||
|
||||
struct RcxDrvVtopRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
uint64_t virtualAddress;
|
||||
};
|
||||
|
||||
struct RcxDrvVtopResponse {
|
||||
uint64_t physicalAddress; /* final translated physical address (with page offset) */
|
||||
uint64_t pml4e; /* raw PML4 entry value */
|
||||
uint64_t pdpte; /* raw PDPT entry value */
|
||||
uint64_t pde; /* raw PD entry value */
|
||||
uint64_t pte; /* raw PT entry value (0 if large/huge page) */
|
||||
uint8_t pageSize; /* 0=4KB, 1=2MB, 2=1GB */
|
||||
uint8_t valid; /* 1 if translation succeeded, 0 if not present */
|
||||
uint8_t _pad2[6];
|
||||
};
|
||||
|
||||
/* ── Compile-time validation ──────────────────────────────────────── */
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(RcxDrvReadRequest) == 24, "ReadRequest layout");
|
||||
static_assert(sizeof(RcxDrvWriteRequest) == 24, "WriteRequest layout");
|
||||
static_assert(sizeof(RcxDrvRegionEntry) == 24, "RegionEntry layout");
|
||||
static_assert(sizeof(RcxDrvModuleEntry) == 536, "ModuleEntry layout");
|
||||
static_assert(sizeof(RcxDrvTebEntry) == 16, "TebEntry layout");
|
||||
static_assert(sizeof(RcxDrvPingResponse) == 8, "PingResponse layout");
|
||||
static_assert(sizeof(RcxDrvReadCr3Response) == 16, "ReadCr3Response layout");
|
||||
static_assert(sizeof(RcxDrvVtopRequest) == 16, "VtopRequest layout");
|
||||
static_assert(sizeof(RcxDrvVtopResponse) == 48, "VtopResponse layout");
|
||||
#endif
|
||||
@@ -45,3 +45,12 @@ set_target_properties(ProcessMemoryPlugin PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||
)
|
||||
|
||||
if(APPLE AND TARGET Reclass)
|
||||
add_custom_command(TARGET ProcessMemoryPlugin POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/../PlugIns"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE:ProcessMemoryPlugin>"
|
||||
"$<TARGET_FILE_DIR:Reclass>/../PlugIns/"
|
||||
COMMENT "Copying ProcessMemoryPlugin into Reclass.app/Contents/PlugIns")
|
||||
endif()
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QImage>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QMap>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(_WIN32)
|
||||
#include <QtWin>
|
||||
#endif
|
||||
@@ -19,6 +20,60 @@
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
typedef struct _UNICODE_STRING { USHORT Length, MaximumLength; PWSTR Buffer; } UNICODE_STRING;
|
||||
typedef struct _CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID;
|
||||
typedef struct _SYSTEM_THREAD_INFORMATION {
|
||||
LARGE_INTEGER KernelTime, UserTime, CreateTime;
|
||||
ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId;
|
||||
LONG Priority, BasePriority; ULONG ContextSwitches, ThreadState, WaitReason;
|
||||
} SYSTEM_THREAD_INFORMATION;
|
||||
typedef struct _SYSTEM_PROCESS_INFORMATION {
|
||||
ULONG NextEntryOffset; // 0x000
|
||||
ULONG NumberOfThreads; // 0x004
|
||||
LARGE_INTEGER WorkingSetPrivateSize; // 0x008
|
||||
ULONG HardFaultCount; // 0x010
|
||||
ULONG NumberOfThreadsHighWatermark; // 0x014
|
||||
ULONGLONG CycleTime; // 0x018
|
||||
LARGE_INTEGER CreateTime; // 0x020
|
||||
LARGE_INTEGER UserTime; // 0x028
|
||||
LARGE_INTEGER KernelTime; // 0x030
|
||||
UNICODE_STRING ImageName; // 0x038
|
||||
LONG BasePriority; // 0x048
|
||||
HANDLE UniqueProcessId; // 0x050
|
||||
PVOID InheritedFromUniqueProcessId; // 0x058
|
||||
ULONG HandleCount; // 0x060
|
||||
ULONG SessionId; // 0x064
|
||||
ULONG_PTR UniqueProcessKey; // 0x068
|
||||
SIZE_T PeakVirtualSize; // 0x070
|
||||
SIZE_T VirtualSize; // 0x078
|
||||
ULONG PageFaultCount; // 0x080
|
||||
ULONG _pad0; // 0x084
|
||||
SIZE_T PeakWorkingSetSize; // 0x088
|
||||
SIZE_T WorkingSetSize; // 0x090
|
||||
SIZE_T QuotaPeakPagedPoolUsage; // 0x098
|
||||
SIZE_T QuotaPagedPoolUsage; // 0x0A0
|
||||
SIZE_T QuotaPeakNonPagedPoolUsage; // 0x0A8
|
||||
SIZE_T QuotaNonPagedPoolUsage; // 0x0B0
|
||||
SIZE_T PagefileUsage; // 0x0B8
|
||||
SIZE_T PeakPagefileUsage; // 0x0C0
|
||||
SIZE_T PrivatePageCount; // 0x0C8
|
||||
LARGE_INTEGER ReadOperationCount; // 0x0D0
|
||||
LARGE_INTEGER WriteOperationCount; // 0x0D8
|
||||
LARGE_INTEGER OtherOperationCount; // 0x0E0
|
||||
LARGE_INTEGER ReadTransferCount; // 0x0E8
|
||||
LARGE_INTEGER WriteTransferCount; // 0x0F0
|
||||
LARGE_INTEGER OtherTransferCount; // 0x0F8
|
||||
} SYSTEM_PROCESS_INFORMATION; // sizeof = 0x100
|
||||
typedef struct alignas(8) _THREAD_BASIC_INFORMATION {
|
||||
NTSTATUS ExitStatus; // 0x00
|
||||
ULONG _pad; // 0x04
|
||||
PVOID TebBaseAddress; // 0x08
|
||||
CLIENT_ID ClientId; // 0x10
|
||||
ULONG_PTR AffinityMask; // 0x20
|
||||
LONG Priority; // 0x28
|
||||
LONG BasePriority; // 0x2C
|
||||
} THREAD_BASIC_INFORMATION;
|
||||
#elif defined(__linux__)
|
||||
#include <climits>
|
||||
#include <sys/types.h>
|
||||
@@ -29,6 +84,13 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#elif defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <libproc.h>
|
||||
#include <sys/proc_info.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -56,8 +118,24 @@ ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& proces
|
||||
m_writable = false;
|
||||
}
|
||||
|
||||
if (m_handle)
|
||||
if (m_handle) {
|
||||
// Detect 32-bit (WoW64) process
|
||||
BOOL isWow64 = FALSE;
|
||||
if (IsWow64Process(m_handle, &isWow64) && isWow64)
|
||||
m_pointerSize = 4;
|
||||
// Query PEB address via NtQueryInformationProcess
|
||||
{
|
||||
typedef NTSTATUS(NTAPI* NtQIP_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
||||
static NtQIP_t pNtQIP = (NtQIP_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
|
||||
if (pNtQIP) {
|
||||
struct { PVOID r1; PVOID PebBaseAddress; PVOID r2[2]; ULONG_PTR pid; PVOID r3; } pbi = {};
|
||||
ULONG retLen = 0;
|
||||
if (pNtQIP(m_handle, /*ProcessBasicInformation*/0, &pbi, sizeof(pbi), &retLen) >= 0 && pbi.PebBaseAddress)
|
||||
m_peb = (uint64_t)(uintptr_t)pbi.PebBaseAddress;
|
||||
}
|
||||
}
|
||||
cacheModules();
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
@@ -65,7 +143,7 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
if (!m_handle || len <= 0) return false;
|
||||
|
||||
SIZE_T bytesRead = 0;
|
||||
ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead);
|
||||
ReadProcessMemory(m_handle, (LPCVOID)(addr), buf, (SIZE_T)len, &bytesRead);
|
||||
if ((int)bytesRead < len)
|
||||
memset((char*)buf + bytesRead, 0, len - bytesRead);
|
||||
return bytesRead > 0;
|
||||
@@ -76,7 +154,7 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
if (!m_handle || !m_writable || len <= 0) return false;
|
||||
|
||||
SIZE_T bytesWritten = 0;
|
||||
if (WriteProcessMemory(m_handle, (LPVOID)(m_base + addr), buf, (SIZE_T)len, &bytesWritten))
|
||||
if (WriteProcessMemory(m_handle, (LPVOID)(addr), buf, (SIZE_T)len, &bytesWritten))
|
||||
return bytesWritten == (SIZE_T)len;
|
||||
return false;
|
||||
}
|
||||
@@ -115,8 +193,14 @@ void ProcessMemoryProvider::cacheModules()
|
||||
if ( i == 0 )
|
||||
m_base = (uint64_t)mi.lpBaseOfDll;
|
||||
|
||||
m_modules.append({
|
||||
WCHAR modPath[MAX_PATH];
|
||||
QString fullPath;
|
||||
if (GetModuleFileNameExW(m_handle, mods[i], modPath, MAX_PATH))
|
||||
fullPath = QString::fromWCharArray(modPath);
|
||||
|
||||
m_modules.push_back(ModuleInfo{
|
||||
QString::fromWCharArray(modName),
|
||||
fullPath,
|
||||
(uint64_t)mi.lpBaseOfDll,
|
||||
(uint64_t)mi.SizeOfImage
|
||||
});
|
||||
@@ -124,6 +208,60 @@ void ProcessMemoryProvider::cacheModules()
|
||||
}
|
||||
}
|
||||
|
||||
QVector<rcx::Provider::ModuleEntry> ProcessMemoryProvider::enumerateModules() const
|
||||
{
|
||||
QVector<ModuleEntry> result;
|
||||
result.reserve(m_modules.size());
|
||||
for (const auto& m : m_modules)
|
||||
result.push_back(ModuleEntry{m.name, m.fullPath, m.base, m.size});
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
if (!m_handle) return regions;
|
||||
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
uint64_t addr = 0;
|
||||
|
||||
while (VirtualQueryEx(m_handle, (LPCVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {
|
||||
if (mbi.State == MEM_COMMIT &&
|
||||
!(mbi.Protect & PAGE_NOACCESS) &&
|
||||
!(mbi.Protect & PAGE_GUARD))
|
||||
{
|
||||
rcx::MemoryRegion region;
|
||||
region.base = (uint64_t)mbi.BaseAddress;
|
||||
region.size = mbi.RegionSize;
|
||||
region.readable = true;
|
||||
region.writable = (mbi.Protect & PAGE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_WRITECOPY) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
||||
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READ) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
||||
|
||||
// Match module name from cached module list
|
||||
for (const auto& mod : m_modules) {
|
||||
if (region.base >= mod.base && region.base < mod.base + mod.size) {
|
||||
region.moduleName = mod.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
|
||||
uint64_t next = (uint64_t)mbi.BaseAddress + mbi.RegionSize;
|
||||
if (next <= addr) break; // overflow protection
|
||||
addr = next;
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||
@@ -147,24 +285,33 @@ ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& proces
|
||||
m_writable = false;
|
||||
}
|
||||
|
||||
if (m_fd >= 0)
|
||||
if (m_fd >= 0) {
|
||||
// Detect 32-bit ELF process
|
||||
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||
QByteArray exePathUtf8 = exePath.toUtf8();
|
||||
int exeFd = ::open(exePathUtf8.constData(), O_RDONLY);
|
||||
if (exeFd >= 0) {
|
||||
unsigned char elfClass = 0;
|
||||
// ELF e_ident[EI_CLASS] is at offset 4
|
||||
if (::pread(exeFd, &elfClass, 1, 4) == 1 && elfClass == 1) // ELFCLASS32
|
||||
m_pointerSize = 4;
|
||||
::close(exeFd);
|
||||
}
|
||||
cacheModules();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
if (m_fd < 0 || len <= 0) return false;
|
||||
|
||||
uint64_t absAddr = m_base + addr;
|
||||
|
||||
// Try process_vm_readv first (faster, no fd seek contention)
|
||||
struct iovec local;
|
||||
local.iov_base = buf;
|
||||
local.iov_len = static_cast<size_t>(len);
|
||||
|
||||
struct iovec remote;
|
||||
remote.iov_base = reinterpret_cast<void*>(absAddr);
|
||||
remote.iov_base = reinterpret_cast<void*>(addr);
|
||||
remote.iov_len = static_cast<size_t>(len);
|
||||
|
||||
ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0);
|
||||
@@ -172,7 +319,7 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
return true;
|
||||
|
||||
// Fallback: pread on /proc/<pid>/mem
|
||||
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
|
||||
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
|
||||
return nread == static_cast<ssize_t>(len);
|
||||
}
|
||||
|
||||
@@ -180,15 +327,13 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
||||
|
||||
uint64_t absAddr = m_base + addr;
|
||||
|
||||
// Try process_vm_writev first
|
||||
struct iovec local;
|
||||
local.iov_base = const_cast<void*>(buf);
|
||||
local.iov_len = static_cast<size_t>(len);
|
||||
|
||||
struct iovec remote;
|
||||
remote.iov_base = reinterpret_cast<void*>(absAddr);
|
||||
remote.iov_base = reinterpret_cast<void*>(addr);
|
||||
remote.iov_len = static_cast<size_t>(len);
|
||||
|
||||
ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0);
|
||||
@@ -196,7 +341,7 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
return true;
|
||||
|
||||
// Fallback: pwrite on /proc/<pid>/mem
|
||||
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
|
||||
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
|
||||
return nwritten == static_cast<ssize_t>(len);
|
||||
}
|
||||
|
||||
@@ -278,16 +423,309 @@ void ProcessMemoryProvider::cacheModules()
|
||||
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
||||
{
|
||||
QFileInfo fi(it.key());
|
||||
m_modules.append({
|
||||
m_modules.push_back(ModuleInfo{
|
||||
fi.fileName(),
|
||||
it.key(),
|
||||
it->base,
|
||||
it->end - it->base
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
if (m_fd < 0) return regions;
|
||||
|
||||
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
||||
std::ifstream mapsFile(mapsPath.toStdString());
|
||||
if (!mapsFile.is_open()) return regions;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(mapsFile, line)) {
|
||||
std::istringstream iss(line);
|
||||
std::string addrRange, perms, offset, dev, inode, pathname;
|
||||
iss >> addrRange >> perms >> offset >> dev >> inode;
|
||||
std::getline(iss, pathname);
|
||||
|
||||
auto dash = addrRange.find('-');
|
||||
if (dash == std::string::npos) continue;
|
||||
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
|
||||
|
||||
if (perms.size() < 4) continue;
|
||||
bool readable = (perms[0] == 'r');
|
||||
bool writable = (perms[1] == 'w');
|
||||
bool executable = (perms[2] == 'x');
|
||||
|
||||
if (!readable) continue;
|
||||
|
||||
rcx::MemoryRegion region;
|
||||
region.base = addrStart;
|
||||
region.size = addrEnd - addrStart;
|
||||
region.readable = readable;
|
||||
region.writable = writable;
|
||||
region.executable = executable;
|
||||
|
||||
// Extract module name from pathname
|
||||
size_t start = pathname.find_first_not_of(" \t");
|
||||
if (start != std::string::npos) {
|
||||
QString qpath = QString::fromStdString(pathname.substr(start));
|
||||
if (qpath.startsWith('/') && !qpath.startsWith("/dev/") &&
|
||||
!qpath.startsWith("/memfd:")) {
|
||||
QFileInfo fi(qpath);
|
||||
region.moduleName = fi.fileName();
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||
: m_task(0)
|
||||
, m_pid(pid)
|
||||
, m_processName(processName)
|
||||
, m_writable(false)
|
||||
, m_base(0)
|
||||
{
|
||||
mach_port_t task = MACH_PORT_NULL;
|
||||
kern_return_t kr = task_for_pid(mach_task_self(), static_cast<int>(pid), &task);
|
||||
if (kr != KERN_SUCCESS || task == MACH_PORT_NULL)
|
||||
return;
|
||||
|
||||
m_task = static_cast<uint32_t>(task);
|
||||
m_writable = true;
|
||||
|
||||
proc_bsdinfo bsdInfo{};
|
||||
int infoLen = proc_pidinfo(static_cast<int>(pid), PROC_PIDTBSDINFO, 0, &bsdInfo, sizeof(bsdInfo));
|
||||
if (infoLen == (int)sizeof(bsdInfo)) {
|
||||
#ifdef PROC_FLAG_LP64
|
||||
m_pointerSize = (bsdInfo.pbi_flags & PROC_FLAG_LP64) ? 8 : 4;
|
||||
#else
|
||||
m_pointerSize = 8;
|
||||
#endif
|
||||
}
|
||||
|
||||
cacheModules();
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
if (m_task == 0 || len <= 0)
|
||||
return false;
|
||||
|
||||
mach_vm_size_t outSize = 0;
|
||||
kern_return_t kr = mach_vm_read_overwrite(
|
||||
static_cast<mach_port_name_t>(m_task),
|
||||
static_cast<mach_vm_address_t>(addr),
|
||||
static_cast<mach_vm_size_t>(len),
|
||||
reinterpret_cast<mach_vm_address_t>(buf),
|
||||
&outSize);
|
||||
|
||||
if ((int)outSize < len)
|
||||
memset((char*)buf + outSize, 0, len - outSize);
|
||||
|
||||
return kr == KERN_SUCCESS && outSize > 0;
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
if (m_task == 0 || !m_writable || len <= 0)
|
||||
return false;
|
||||
|
||||
kern_return_t kr = mach_vm_write(
|
||||
static_cast<mach_port_name_t>(m_task),
|
||||
static_cast<mach_vm_address_t>(addr),
|
||||
reinterpret_cast<vm_offset_t>(const_cast<void*>(buf)),
|
||||
static_cast<mach_msg_type_number_t>(len));
|
||||
return kr == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (m_task == 0)
|
||||
return;
|
||||
|
||||
m_modules.clear();
|
||||
|
||||
char mainPathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
QString mainPath;
|
||||
if (proc_pidpath((int)m_pid, mainPathBuf, sizeof(mainPathBuf)) > 0)
|
||||
mainPath = QString::fromUtf8(mainPathBuf);
|
||||
|
||||
struct Range { uint64_t base; uint64_t end; };
|
||||
QMap<QString, Range> moduleRanges;
|
||||
|
||||
mach_vm_address_t addr = 0;
|
||||
uint32_t depth = 0;
|
||||
for (;;) {
|
||||
mach_vm_size_t size = 0;
|
||||
vm_region_submap_info_data_64_t info{};
|
||||
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
kern_return_t kr = mach_vm_region_recurse(
|
||||
static_cast<mach_port_name_t>(m_task),
|
||||
&addr,
|
||||
&size,
|
||||
&depth,
|
||||
reinterpret_cast<vm_region_recurse_info_t>(&info),
|
||||
&count);
|
||||
if (kr != KERN_SUCCESS)
|
||||
break;
|
||||
|
||||
if (info.is_submap) {
|
||||
++depth;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
++addr;
|
||||
continue;
|
||||
}
|
||||
|
||||
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
int pathLen = proc_regionfilename((int)m_pid, (uint64_t)addr, pathBuf, sizeof(pathBuf));
|
||||
if (pathLen > 0) {
|
||||
QString fullPath = QString::fromUtf8(pathBuf);
|
||||
|
||||
uint64_t regionBase = (uint64_t)addr;
|
||||
uint64_t regionEnd = regionBase + (uint64_t)size;
|
||||
auto it = moduleRanges.find(fullPath);
|
||||
if (it == moduleRanges.end()) {
|
||||
moduleRanges.insert(fullPath, {regionBase, regionEnd});
|
||||
} else {
|
||||
if (regionBase < it->base) it->base = regionBase;
|
||||
if (regionEnd > it->end) it->end = regionEnd;
|
||||
}
|
||||
|
||||
if (m_base == 0 && !mainPath.isEmpty() && fullPath == mainPath && (info.protection & VM_PROT_EXECUTE))
|
||||
m_base = regionBase;
|
||||
}
|
||||
|
||||
uint64_t next = (uint64_t)addr + (uint64_t)size;
|
||||
if (next <= (uint64_t)addr)
|
||||
break;
|
||||
addr = (mach_vm_address_t)next;
|
||||
}
|
||||
|
||||
m_modules.reserve(moduleRanges.size());
|
||||
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
||||
{
|
||||
QFileInfo fi(it.key());
|
||||
m_modules.push_back(ModuleInfo{
|
||||
fi.fileName(),
|
||||
it.key(),
|
||||
it->base,
|
||||
it->end - it->base
|
||||
});
|
||||
}
|
||||
|
||||
if (m_base == 0 && !m_modules.isEmpty())
|
||||
m_base = m_modules.front().base;
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
if (m_task == 0)
|
||||
return regions;
|
||||
|
||||
mach_vm_address_t addr = 0;
|
||||
uint32_t depth = 0;
|
||||
for (;;) {
|
||||
mach_vm_size_t size = 0;
|
||||
vm_region_submap_info_data_64_t info{};
|
||||
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
kern_return_t kr = mach_vm_region_recurse(
|
||||
static_cast<mach_port_name_t>(m_task),
|
||||
&addr,
|
||||
&size,
|
||||
&depth,
|
||||
reinterpret_cast<vm_region_recurse_info_t>(&info),
|
||||
&count);
|
||||
if (kr != KERN_SUCCESS)
|
||||
break;
|
||||
|
||||
if (info.is_submap) {
|
||||
++depth;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
++addr;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool readable = (info.protection & VM_PROT_READ) != 0;
|
||||
if (readable)
|
||||
{
|
||||
rcx::MemoryRegion region;
|
||||
region.base = (uint64_t)addr;
|
||||
region.size = (uint64_t)size;
|
||||
region.readable = readable;
|
||||
region.writable = (info.protection & VM_PROT_WRITE) != 0;
|
||||
region.executable = (info.protection & VM_PROT_EXECUTE) != 0;
|
||||
|
||||
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
int pathLen = proc_regionfilename((int)m_pid, region.base, pathBuf, sizeof(pathBuf));
|
||||
if (pathLen > 0) {
|
||||
QFileInfo fi(QString::fromUtf8(pathBuf));
|
||||
region.moduleName = fi.fileName();
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
|
||||
uint64_t next = (uint64_t)addr + (uint64_t)size;
|
||||
if (next <= (uint64_t)addr)
|
||||
break;
|
||||
addr = (mach_vm_address_t)next;
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
#endif // platform
|
||||
|
||||
#ifndef _WIN32
|
||||
QVector<rcx::Provider::ModuleEntry> ProcessMemoryProvider::enumerateModules() const
|
||||
{
|
||||
QVector<ModuleEntry> result;
|
||||
result.reserve(m_modules.size());
|
||||
for (const auto& m : m_modules)
|
||||
result.push_back(ModuleEntry{m.name, m.fullPath, m.base, m.size});
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
@@ -296,6 +734,9 @@ ProcessMemoryProvider::~ProcessMemoryProvider()
|
||||
#elif defined(__linux__)
|
||||
if (m_fd >= 0)
|
||||
::close(m_fd);
|
||||
#elif defined(__APPLE__)
|
||||
if (m_task != 0)
|
||||
mach_port_deallocate(mach_task_self(), static_cast<mach_port_name_t>(m_task));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -305,9 +746,63 @@ int ProcessMemoryProvider::size() const
|
||||
return m_handle ? 0x10000 : 0;
|
||||
#elif defined(__linux__)
|
||||
return (m_fd >= 0) ? 0x10000 : 0;
|
||||
#elif defined(__APPLE__)
|
||||
return (m_task != 0) ? 0x10000 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
QVector<rcx::Provider::ThreadInfo> ProcessMemoryProvider::tebs() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
QVector<ThreadInfo> result;
|
||||
if (!m_handle || !m_peb) return result;
|
||||
|
||||
typedef NTSTATUS(NTAPI* NtQSI_t)(ULONG, PVOID, ULONG, PULONG);
|
||||
typedef NTSTATUS(NTAPI* NtQIT_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
||||
static auto pNtQSI = (NtQSI_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
|
||||
static auto pNtQIT = (NtQIT_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread");
|
||||
if (!pNtQSI || !pNtQIT) return result;
|
||||
|
||||
// Enumerate threads via SystemProcessInformation (class 5)
|
||||
ULONG retLen = 0;
|
||||
ULONG bufSize = 1 << 20;
|
||||
QByteArray buf(bufSize, 0);
|
||||
NTSTATUS qsiSt;
|
||||
for (int attempt = 0; attempt < 8; ++attempt) {
|
||||
qsiSt = pNtQSI(5, buf.data(), bufSize, &retLen);
|
||||
if ((uint32_t)qsiSt != 0xC0000004u) break;
|
||||
bufSize *= 2;
|
||||
buf.resize(bufSize);
|
||||
}
|
||||
if (qsiSt < 0) return result;
|
||||
|
||||
// Walk process entries to find ours
|
||||
auto* proc = (SYSTEM_PROCESS_INFORMATION*)buf.data();
|
||||
for (;;) {
|
||||
if ((uintptr_t)proc->UniqueProcessId == m_pid) {
|
||||
auto* threads = (SYSTEM_THREAD_INFORMATION*)((char*)proc + sizeof(*proc));
|
||||
for (ULONG i = 0; i < proc->NumberOfThreads; ++i) {
|
||||
DWORD tid = (DWORD)(uintptr_t)threads[i].ClientId.UniqueThread;
|
||||
HANDLE hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, tid);
|
||||
if (!hThread) continue;
|
||||
THREAD_BASIC_INFORMATION tbi = {};
|
||||
ULONG tbiLen = 0;
|
||||
NTSTATUS qitSt = pNtQIT(hThread, 0, &tbi, sizeof(tbi), &tbiLen);
|
||||
if (qitSt >= 0 && tbi.TebBaseAddress)
|
||||
result.push_back(ThreadInfo{(uint64_t)(uintptr_t)tbi.TebBaseAddress, tid});
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!proc->NextEntryOffset) break;
|
||||
proc = (SYSTEM_PROCESS_INFORMATION*)((char*)proc + proc->NextEntryOffset);
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// ProcessMemoryPlugin implementation
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -403,6 +898,68 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__APPLE__)
|
||||
QStringList parts = target.split(':');
|
||||
bool ok = false;
|
||||
uint32_t pid = parts[0].toUInt(&ok);
|
||||
if (!ok || pid == 0)
|
||||
return 0;
|
||||
|
||||
mach_port_t task = MACH_PORT_NULL;
|
||||
kern_return_t tkr = task_for_pid(mach_task_self(), static_cast<int>(pid), &task);
|
||||
if (tkr != KERN_SUCCESS || task == MACH_PORT_NULL)
|
||||
return 0;
|
||||
|
||||
char mainPathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
QString mainPath;
|
||||
if (proc_pidpath((int)pid, mainPathBuf, sizeof(mainPathBuf)) > 0)
|
||||
mainPath = QString::fromUtf8(mainPathBuf);
|
||||
|
||||
uint64_t base = 0;
|
||||
mach_vm_address_t addr = 0;
|
||||
uint32_t depth = 0;
|
||||
for (;;) {
|
||||
mach_vm_size_t size = 0;
|
||||
vm_region_submap_info_data_64_t info{};
|
||||
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
kern_return_t kr = mach_vm_region_recurse(task, &addr, &size, &depth,
|
||||
reinterpret_cast<vm_region_recurse_info_t>(&info),
|
||||
&count);
|
||||
if (kr != KERN_SUCCESS)
|
||||
break;
|
||||
|
||||
if (info.is_submap) {
|
||||
++depth;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
++addr;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((info.protection & VM_PROT_EXECUTE) != 0) {
|
||||
if (!mainPath.isEmpty()) {
|
||||
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
int pathLen = proc_regionfilename((int)pid, (uint64_t)addr, pathBuf, sizeof(pathBuf));
|
||||
if (pathLen > 0 && QString::fromUtf8(pathBuf) == mainPath) {
|
||||
base = (uint64_t)addr;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
base = (uint64_t)addr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t next = (uint64_t)addr + (uint64_t)size;
|
||||
if (next <= (uint64_t)addr)
|
||||
break;
|
||||
addr = (mach_vm_address_t)next;
|
||||
}
|
||||
|
||||
mach_port_deallocate(mach_task_self(), task);
|
||||
return base;
|
||||
#else
|
||||
Q_UNUSED(target);
|
||||
return 0;
|
||||
@@ -423,6 +980,7 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
info.name = pinfo.name;
|
||||
info.path = pinfo.path;
|
||||
info.icon = pinfo.icon;
|
||||
info.is32Bit = pinfo.is32Bit;
|
||||
processes.append(info);
|
||||
}
|
||||
|
||||
@@ -484,6 +1042,11 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||
}
|
||||
}
|
||||
|
||||
// Detect 32-bit (WoW64) process
|
||||
BOOL isWow64 = FALSE;
|
||||
if (IsWow64Process(hProcess, &isWow64) && isWow64)
|
||||
info.is32Bit = true;
|
||||
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
|
||||
@@ -530,6 +1093,71 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||
info.name = procName;
|
||||
info.path = resolvedPath;
|
||||
info.icon = defaultIcon;
|
||||
|
||||
// Detect 32-bit ELF process
|
||||
int exeFd = ::open(exePath.toUtf8().constData(), O_RDONLY);
|
||||
if (exeFd >= 0) {
|
||||
unsigned char elfClass = 0;
|
||||
if (::pread(exeFd, &elfClass, 1, 4) == 1 && elfClass == 1) // ELFCLASS32
|
||||
info.is32Bit = true;
|
||||
::close(exeFd);
|
||||
}
|
||||
|
||||
processes.append(info);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||
|
||||
int bytes = proc_listpids(PROC_ALL_PIDS, 0, nullptr, 0);
|
||||
if (bytes <= 0)
|
||||
return processes;
|
||||
|
||||
int count = bytes / (int)sizeof(pid_t);
|
||||
QVector<pid_t> pids(count);
|
||||
bytes = proc_listpids(PROC_ALL_PIDS, 0, pids.data(), count * (int)sizeof(pid_t));
|
||||
if (bytes <= 0)
|
||||
return processes;
|
||||
|
||||
count = bytes / (int)sizeof(pid_t);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
pid_t pid = pids[i];
|
||||
if (pid <= 0)
|
||||
continue;
|
||||
|
||||
mach_port_t task = MACH_PORT_NULL;
|
||||
if (task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS || task == MACH_PORT_NULL)
|
||||
continue;
|
||||
mach_port_deallocate(mach_task_self(), task);
|
||||
|
||||
char nameBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
int nameLen = proc_name(pid, nameBuf, sizeof(nameBuf));
|
||||
QString procName;
|
||||
if (nameLen > 0)
|
||||
procName = QString::fromUtf8(nameBuf);
|
||||
if (procName.isEmpty())
|
||||
continue;
|
||||
|
||||
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
||||
QString procPath;
|
||||
if (proc_pidpath(pid, pathBuf, sizeof(pathBuf)) > 0)
|
||||
procPath = QString::fromUtf8(pathBuf);
|
||||
|
||||
PluginProcessInfo info;
|
||||
info.pid = static_cast<uint32_t>(pid);
|
||||
info.name = procName;
|
||||
info.path = procPath;
|
||||
info.icon = defaultIcon;
|
||||
|
||||
proc_bsdinfo bsdInfo{};
|
||||
int infoLen = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdInfo, sizeof(bsdInfo));
|
||||
if (infoLen == (int)sizeof(bsdInfo)) {
|
||||
#ifdef PROC_FLAG_LP64
|
||||
info.is32Bit = (bsdInfo.pbi_flags & PROC_FLAG_LP64) == 0;
|
||||
#else
|
||||
info.is32Bit = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
processes.append(info);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -24,15 +24,28 @@ public:
|
||||
QString name() const override { return m_processName; }
|
||||
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
||||
QString getSymbol(uint64_t addr) const override;
|
||||
uint64_t symbolToAddress(const QString& name) const override;
|
||||
|
||||
bool isLive() const override { return true; }
|
||||
uint64_t base() const override { return m_base; }
|
||||
void setBase(uint64_t b) override { m_base = b; }
|
||||
int pointerSize() const override { return m_pointerSize; }
|
||||
QVector<rcx::MemoryRegion> enumerateRegions() const override;
|
||||
bool isReadable(uint64_t, int len) const override {
|
||||
#ifdef _WIN32
|
||||
return m_handle && len >= 0;
|
||||
#elif defined(__linux__)
|
||||
return m_fd >= 0 && len >= 0;
|
||||
#elif defined(__APPLE__)
|
||||
return m_task != 0 && len >= 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Process-specific helpers
|
||||
uint32_t pid() const { return m_pid; }
|
||||
uint64_t baseAddress() const { return m_base; }
|
||||
void refreshModules() { m_modules.clear(); cacheModules(); }
|
||||
uint64_t peb() const override { return m_peb; }
|
||||
QVector<ThreadInfo> tebs() const override;
|
||||
QVector<ModuleEntry> enumerateModules() const override;
|
||||
|
||||
private:
|
||||
void cacheModules();
|
||||
@@ -42,14 +55,19 @@ private:
|
||||
void* m_handle;
|
||||
#elif defined(__linux__)
|
||||
int m_fd;
|
||||
#elif defined(__APPLE__)
|
||||
uint32_t m_task;
|
||||
#endif
|
||||
uint32_t m_pid;
|
||||
QString m_processName;
|
||||
bool m_writable;
|
||||
uint64_t m_base;
|
||||
int m_pointerSize = 8;
|
||||
uint64_t m_peb = 0;
|
||||
|
||||
struct ModuleInfo {
|
||||
QString name;
|
||||
QString fullPath;
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
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.push_back(RemoteProcessProvider::ModuleInfo{modName, entry->base, entry->size});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ping()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (!connected) return false;
|
||||
|
||||
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
|
||||
hdr->command = RPC_CMD_PING;
|
||||
hdr->status = RCX_RPC_STATUS_OK;
|
||||
|
||||
if (!signalAndWait()) { connected = false; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (!connected) return;
|
||||
|
||||
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
|
||||
hdr->command = RPC_CMD_SHUTDOWN;
|
||||
hdr->status = RCX_RPC_STATUS_OK;
|
||||
|
||||
signalAndWait(500);
|
||||
connected = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* RemoteProcessProvider
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
RemoteProcessProvider::RemoteProcessProvider(
|
||||
uint32_t pid, const QString& processName,
|
||||
std::shared_ptr<IpcClient> ipc)
|
||||
: m_pid(pid)
|
||||
, m_processName(processName)
|
||||
, m_connected(ipc && ipc->connected)
|
||||
, m_base(0)
|
||||
, m_ipc(std::move(ipc))
|
||||
{
|
||||
if (m_connected) {
|
||||
cacheModules();
|
||||
// Read pointer size from payload's SHM header (0 means not set → default 8)
|
||||
auto* hdr = m_ipc ? m_ipc->header() : nullptr;
|
||||
if (hdr) {
|
||||
uint32_t ps = hdr->pointerSize;
|
||||
if (ps == 4 || ps == 8)
|
||||
m_pointerSize = (int)ps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoteProcessProvider::~RemoteProcessProvider() = default;
|
||||
|
||||
bool RemoteProcessProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
if (!m_connected || len <= 0) return false;
|
||||
bool ok = m_ipc->readSingle(addr, buf, len);
|
||||
if (!ok) {
|
||||
memset(buf, 0, (size_t)len);
|
||||
/* update connectivity flag through mutable ipc */
|
||||
const_cast<RemoteProcessProvider*>(this)->m_connected = m_ipc->connected;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
int RemoteProcessProvider::size() const
|
||||
{
|
||||
return m_connected ? 0x10000 : 0;
|
||||
}
|
||||
|
||||
bool RemoteProcessProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
if (!m_connected || len <= 0) return false;
|
||||
bool ok = m_ipc->writeSingle(addr, buf, len);
|
||||
if (!ok) m_connected = m_ipc->connected;
|
||||
return ok;
|
||||
}
|
||||
|
||||
QString RemoteProcessProvider::getSymbol(uint64_t addr) const
|
||||
{
|
||||
for (const auto& mod : m_modules) {
|
||||
if (addr >= mod.base && addr < mod.base + mod.size) {
|
||||
uint64_t off = addr - mod.base;
|
||||
return QStringLiteral("%1+0x%2")
|
||||
.arg(mod.name)
|
||||
.arg(off, 0, 16, QChar('0'));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t RemoteProcessProvider::symbolToAddress(const QString& n) const
|
||||
{
|
||||
for (const auto& mod : m_modules) {
|
||||
if (mod.name.compare(n, Qt::CaseInsensitive) == 0)
|
||||
return mod.base;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RemoteProcessProvider::cacheModules()
|
||||
{
|
||||
m_modules = m_ipc->enumerateModules();
|
||||
if (!m_modules.isEmpty())
|
||||
m_base = m_modules.first().base;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* Injection helpers
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
namespace {
|
||||
|
||||
/* Resolve payload DLL/SO path next to this plugin DLL/SO */
|
||||
static QString payloadPath()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
HMODULE hSelf = nullptr;
|
||||
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(&payloadPath), &hSelf);
|
||||
WCHAR buf[MAX_PATH];
|
||||
GetModuleFileNameW(hSelf, buf, MAX_PATH);
|
||||
QFileInfo fi(QString::fromWCharArray(buf));
|
||||
return fi.absolutePath() + QStringLiteral("/rcx_payload.dll");
|
||||
#else
|
||||
Dl_info info;
|
||||
dladdr(reinterpret_cast<void*>(&payloadPath), &info);
|
||||
QFileInfo fi(QString::fromUtf8(info.dli_fname));
|
||||
return fi.absolutePath() + QStringLiteral("/rcx_payload.so");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/* ── Windows injection: CreateRemoteThread + LoadLibraryA ─────────── */
|
||||
|
||||
static bool injectPayload(uint32_t pid, QString* errorMsg)
|
||||
{
|
||||
QString path = payloadPath();
|
||||
QByteArray pathUtf8 = QDir::toNativeSeparators(path).toLocal8Bit();
|
||||
|
||||
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||||
if (!hProc) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("OpenProcess failed (error %1).\n"
|
||||
"Try running as Administrator.")
|
||||
.arg(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
/* allocate + write path string in target */
|
||||
SIZE_T pathLen = (SIZE_T)(pathUtf8.size() + 1);
|
||||
void* remotePath = VirtualAllocEx(hProc, nullptr, pathLen,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remotePath) {
|
||||
if (errorMsg) *errorMsg = QStringLiteral("VirtualAllocEx failed.");
|
||||
CloseHandle(hProc);
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteProcessMemory(hProc, remotePath, pathUtf8.constData(), pathLen, nullptr);
|
||||
|
||||
/* Step 1: LoadLibraryA — loads the DLL (DllMain is minimal) */
|
||||
HMODULE hK32 = GetModuleHandleA("kernel32.dll");
|
||||
auto pLoadLib = reinterpret_cast<LPTHREAD_START_ROUTINE>(
|
||||
GetProcAddress(hK32, "LoadLibraryA"));
|
||||
|
||||
HANDLE hThread = CreateRemoteThread(hProc, nullptr, 0,
|
||||
pLoadLib, remotePath, 0, nullptr);
|
||||
if (!hThread) {
|
||||
if (errorMsg) *errorMsg = QStringLiteral("CreateRemoteThread failed (error %1).")
|
||||
.arg(GetLastError());
|
||||
VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProc);
|
||||
return false;
|
||||
}
|
||||
|
||||
WaitForSingleObject(hThread, 10000);
|
||||
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeThread(hThread, &exitCode);
|
||||
CloseHandle(hThread);
|
||||
|
||||
VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE);
|
||||
|
||||
if (exitCode == 0) {
|
||||
CloseHandle(hProc);
|
||||
if (errorMsg) *errorMsg = QStringLiteral("LoadLibrary returned NULL in target.\n"
|
||||
"Ensure rcx_payload.dll is in: %1").arg(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 2: Call RcxPayloadInit() — safe to create timer queues now
|
||||
(loader lock is no longer held after LoadLibrary returned) */
|
||||
HMODULE hPayloadRemote = (HMODULE)(uintptr_t)exitCode;
|
||||
auto pGetProcAddr = reinterpret_cast<FARPROC(WINAPI*)(HMODULE, LPCSTR)>(
|
||||
GetProcAddress(hK32, "GetProcAddress"));
|
||||
|
||||
/* Write "RcxPayloadInit\0" into target, call GetProcAddress remotely */
|
||||
const char initName[] = "RcxPayloadInit";
|
||||
void* remoteInitName = VirtualAllocEx(hProc, nullptr, sizeof(initName),
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (remoteInitName) {
|
||||
WriteProcessMemory(hProc, remoteInitName, initName, sizeof(initName), nullptr);
|
||||
|
||||
/* We need to call GetProcAddress(hPayload, "RcxPayloadInit") then call the result.
|
||||
Simpler approach: write small shellcode that does both calls. */
|
||||
uint8_t shellcode[128];
|
||||
int off = 0;
|
||||
|
||||
/* sub rsp, 40 ; shadow space + alignment */
|
||||
shellcode[off++] = 0x48; shellcode[off++] = 0x83; shellcode[off++] = 0xEC; shellcode[off++] = 0x28;
|
||||
/* mov rcx, hPayloadRemote ; first arg = module handle */
|
||||
shellcode[off++] = 0x48; shellcode[off++] = 0xB9;
|
||||
uint64_t hMod = (uint64_t)(uintptr_t)hPayloadRemote;
|
||||
memcpy(shellcode + off, &hMod, 8); off += 8;
|
||||
/* mov rdx, remoteInitName ; second arg = "RcxPayloadInit" */
|
||||
shellcode[off++] = 0x48; shellcode[off++] = 0xBA;
|
||||
uint64_t pName = (uint64_t)(uintptr_t)remoteInitName;
|
||||
memcpy(shellcode + off, &pName, 8); off += 8;
|
||||
/* mov rax, GetProcAddress */
|
||||
shellcode[off++] = 0x48; shellcode[off++] = 0xB8;
|
||||
uint64_t pGPA = (uint64_t)(uintptr_t)pGetProcAddr;
|
||||
memcpy(shellcode + off, &pGPA, 8); off += 8;
|
||||
/* call rax ; rax = RcxPayloadInit */
|
||||
shellcode[off++] = 0xFF; shellcode[off++] = 0xD0;
|
||||
/* test rax, rax */
|
||||
shellcode[off++] = 0x48; shellcode[off++] = 0x85; shellcode[off++] = 0xC0;
|
||||
/* jz skip (jump over the call if null) */
|
||||
shellcode[off++] = 0x74; shellcode[off++] = 0x02;
|
||||
/* call rax ; RcxPayloadInit() */
|
||||
shellcode[off++] = 0xFF; shellcode[off++] = 0xD0;
|
||||
/* skip: add rsp, 40 */
|
||||
shellcode[off++] = 0x48; shellcode[off++] = 0x83; shellcode[off++] = 0xC4; shellcode[off++] = 0x28;
|
||||
/* ret */
|
||||
shellcode[off++] = 0xC3;
|
||||
|
||||
void* remoteCode = VirtualAllocEx(hProc, nullptr, (SIZE_T)off,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (remoteCode) {
|
||||
WriteProcessMemory(hProc, remoteCode, shellcode, (SIZE_T)off, nullptr);
|
||||
|
||||
HANDLE hThread2 = CreateRemoteThread(hProc, nullptr, 0,
|
||||
(LPTHREAD_START_ROUTINE)remoteCode, nullptr, 0, nullptr);
|
||||
if (hThread2) {
|
||||
WaitForSingleObject(hThread2, 10000);
|
||||
CloseHandle(hThread2);
|
||||
}
|
||||
VirtualFreeEx(hProc, remoteCode, 0, MEM_RELEASE);
|
||||
}
|
||||
VirtualFreeEx(hProc, remoteInitName, 0, MEM_RELEASE);
|
||||
}
|
||||
|
||||
CloseHandle(hProc);
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
/* ── Linux injection: ptrace + dlopen ─────────────────────────────── */
|
||||
|
||||
static uint64_t findLibBase(pid_t pid, const char* libName)
|
||||
{
|
||||
char mapsPath[64];
|
||||
snprintf(mapsPath, sizeof(mapsPath), "/proc/%d/maps", pid);
|
||||
FILE* f = fopen(mapsPath, "r");
|
||||
if (!f) return 0;
|
||||
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strstr(line, libName)) {
|
||||
uint64_t base;
|
||||
if (sscanf(line, "%lx-", &base) == 1) {
|
||||
fclose(f);
|
||||
return base;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t findSyscallInsn(pid_t pid)
|
||||
{
|
||||
char mapsPath[64];
|
||||
snprintf(mapsPath, sizeof(mapsPath), "/proc/%d/maps", pid);
|
||||
FILE* f = fopen(mapsPath, "r");
|
||||
if (!f) return 0;
|
||||
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strstr(line, "libc") && strstr(line, "r-xp")) {
|
||||
uint64_t start, end;
|
||||
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
|
||||
fclose(f);
|
||||
|
||||
/* scan for 0F 05 (syscall) */
|
||||
char memPath[64];
|
||||
snprintf(memPath, sizeof(memPath), "/proc/%d/mem", pid);
|
||||
int memFd = open(memPath, O_RDONLY);
|
||||
if (memFd < 0) return 0;
|
||||
|
||||
uint8_t buf[4096];
|
||||
for (uint64_t off = start; off < end; off += sizeof(buf)) {
|
||||
ssize_t n = pread(memFd, buf, sizeof(buf), (off_t)off);
|
||||
if (n <= 1) break;
|
||||
for (ssize_t i = 0; i + 1 < n; ++i) {
|
||||
if (buf[i] == 0x0F && buf[i + 1] == 0x05) {
|
||||
close(memFd);
|
||||
return off + (uint64_t)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(memFd);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool writeTargetMem(pid_t pid, uint64_t addr, const void* src, size_t len)
|
||||
{
|
||||
const uint8_t* p = static_cast<const uint8_t*>(src);
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long val = 0;
|
||||
size_t chunk = (len - i < sizeof(long)) ? (len - i) : sizeof(long);
|
||||
if (chunk < sizeof(long)) {
|
||||
errno = 0;
|
||||
val = ptrace(PTRACE_PEEKDATA, pid, (void*)(addr + i), nullptr);
|
||||
if (errno) return false;
|
||||
}
|
||||
memcpy(&val, p + i, chunk);
|
||||
if (ptrace(PTRACE_POKEDATA, pid, (void*)(addr + i), (void*)val) < 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool injectPayload(uint32_t pid, QString* errorMsg)
|
||||
{
|
||||
QString path = payloadPath();
|
||||
QByteArray pathUtf8 = path.toUtf8();
|
||||
|
||||
if (ptrace(PTRACE_ATTACH, (pid_t)pid, nullptr, nullptr) < 0) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("ptrace attach failed: %1\n"
|
||||
"Check /proc/sys/kernel/yama/ptrace_scope or run as root.")
|
||||
.arg(strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int status;
|
||||
waitpid((pid_t)pid, &status, 0);
|
||||
|
||||
/* save registers */
|
||||
struct user_regs_struct savedRegs, regs;
|
||||
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &savedRegs);
|
||||
regs = savedRegs;
|
||||
|
||||
/* find syscall instruction in target's libc */
|
||||
uint64_t syscallAddr = findSyscallInsn((pid_t)pid);
|
||||
if (!syscallAddr) {
|
||||
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
|
||||
if (errorMsg) *errorMsg = QStringLiteral("Could not find syscall instruction in target.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* find dlopen in target via libc offset technique */
|
||||
void* ourDlopen = dlsym(RTLD_DEFAULT, "dlopen");
|
||||
uint64_t ourLibcBase = findLibBase(getpid(), "libc");
|
||||
uint64_t targetLibcBase = findLibBase((pid_t)pid, "libc");
|
||||
|
||||
if (!ourDlopen || !ourLibcBase || !targetLibcBase) {
|
||||
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
|
||||
if (errorMsg) *errorMsg = QStringLiteral("Could not resolve dlopen address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t targetDlopen = targetLibcBase + ((uint64_t)ourDlopen - ourLibcBase);
|
||||
|
||||
/* call mmap in target via syscall: mmap(0, 4096, RWX, MAP_PRIVATE|MAP_ANON, -1, 0) */
|
||||
regs.rax = 9; /* __NR_mmap */
|
||||
regs.rdi = 0;
|
||||
regs.rsi = 4096;
|
||||
regs.rdx = 7; /* PROT_READ|PROT_WRITE|PROT_EXEC */
|
||||
regs.r10 = 0x22; /* MAP_PRIVATE|MAP_ANONYMOUS */
|
||||
regs.r8 = (uint64_t)-1;
|
||||
regs.r9 = 0;
|
||||
regs.rip = syscallAddr;
|
||||
|
||||
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, ®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;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ set(PLUGIN_SOURCES
|
||||
add_library(WinDbgMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||
|
||||
# Link Qt + DbgEng
|
||||
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets dbgeng ole32)
|
||||
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets ole32)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(WinDbgMemoryPlugin PRIVATE
|
||||
|
||||
@@ -12,12 +12,99 @@
|
||||
#include <QDebug>
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <initguid.h>
|
||||
#include <dbgeng.h>
|
||||
#pragma comment(lib, "dbgeng.lib")
|
||||
// dbgeng.dll is loaded dynamically — see loadDbgEngTools()
|
||||
|
||||
// The system dbgeng.dll (C:\Windows\System32) does not support remote
|
||||
// connections (DebugConnect returns 0x8007053d). The full version lives
|
||||
// in the Debugging Tools for Windows directory. We load it dynamically
|
||||
// so the plugin works without requiring the debugger tools on PATH.
|
||||
static const char* const kDbgToolsDirs[] = {
|
||||
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64",
|
||||
"C:\\Program Files\\Windows Kits\\10\\Debuggers\\x64",
|
||||
};
|
||||
static const char* const kSettingsKey = "WinDbgPlugin/DbgToolsDir";
|
||||
|
||||
typedef HRESULT (STDAPICALLTYPE *PFN_DebugConnect)(PCSTR, REFIID, PVOID*);
|
||||
typedef HRESULT (STDAPICALLTYPE *PFN_DebugCreate)(REFIID, PVOID*);
|
||||
|
||||
static QString s_loadedDir;
|
||||
static HMODULE s_hDbgEng = nullptr;
|
||||
|
||||
static HMODULE tryLoadFrom(const char* dir) {
|
||||
SetDllDirectoryA(dir);
|
||||
// Pre-load dependencies so the tools versions are used instead of
|
||||
// the older System32 copies (e.g. dbghelp.dll without StackWalk2).
|
||||
char path[MAX_PATH];
|
||||
for (auto dep : {"dbghelp.dll", "dbgcore.dll", "symsrv.dll"}) {
|
||||
snprintf(path, sizeof(path), "%s\\%s", dir, dep);
|
||||
LoadLibraryA(path); // OK if missing
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s\\dbgeng.dll", dir);
|
||||
HMODULE h = LoadLibraryA(path);
|
||||
if (h) {
|
||||
s_loadedDir = QString::fromLocal8Bit(dir);
|
||||
qDebug() << "[WinDbg] Loaded dbgeng.dll from" << dir;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static HMODULE loadDbgEngTools() {
|
||||
if (s_hDbgEng) return s_hDbgEng;
|
||||
|
||||
// 1. Try user-configured path from settings
|
||||
QSettings settings;
|
||||
QString userDir = settings.value(kSettingsKey).toString();
|
||||
if (!userDir.isEmpty()) {
|
||||
s_hDbgEng = tryLoadFrom(userDir.toLocal8Bit().constData());
|
||||
if (s_hDbgEng) return s_hDbgEng;
|
||||
}
|
||||
|
||||
// 2. Try well-known install paths
|
||||
for (auto dir : kDbgToolsDirs) {
|
||||
s_hDbgEng = tryLoadFrom(dir);
|
||||
if (s_hDbgEng) return s_hDbgEng;
|
||||
}
|
||||
|
||||
SetDllDirectoryA(nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool dbgToolsFound() {
|
||||
loadDbgEngTools();
|
||||
return s_hDbgEng != nullptr;
|
||||
}
|
||||
|
||||
static PFN_DebugConnect getDebugConnect() {
|
||||
static PFN_DebugConnect pfn = nullptr;
|
||||
static bool tried = false;
|
||||
if (!tried) {
|
||||
tried = true;
|
||||
HMODULE h = loadDbgEngTools();
|
||||
if (h) pfn = (PFN_DebugConnect)GetProcAddress(h, "DebugConnect");
|
||||
if (!pfn) qWarning() << "[WinDbg] DebugConnect not available — Debugging Tools not found";
|
||||
}
|
||||
return pfn;
|
||||
}
|
||||
|
||||
static PFN_DebugCreate getDebugCreate() {
|
||||
static PFN_DebugCreate pfn = nullptr;
|
||||
static bool tried = false;
|
||||
if (!tried) {
|
||||
tried = true;
|
||||
HMODULE h = loadDbgEngTools();
|
||||
if (h) pfn = (PFN_DebugCreate)GetProcAddress(h, "DebugCreate");
|
||||
if (!pfn) qWarning() << "[WinDbg] DebugCreate not available — Debugging Tools not found";
|
||||
}
|
||||
return pfn;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -65,6 +152,9 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
||||
dispatchToOwner([this, &target]() {
|
||||
HRESULT hr;
|
||||
|
||||
// COM must be initialized on this thread for DbgEng
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
qDebug() << "[WinDbg] Opening target:" << target
|
||||
<< "on DbgEng thread" << QThread::currentThread();
|
||||
|
||||
@@ -72,9 +162,11 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
||||
|| target.startsWith("npipe:", Qt::CaseInsensitive))
|
||||
{
|
||||
// ── Remote: connect to existing WinDbg debug server ──
|
||||
auto pfnConnect = getDebugConnect();
|
||||
if (!pfnConnect) { qWarning() << "[WinDbg] Debugging Tools required for remote connections"; return; }
|
||||
QByteArray connUtf8 = target.toUtf8();
|
||||
qDebug() << "[WinDbg] DebugConnect:" << target;
|
||||
hr = DebugConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
|
||||
hr = pfnConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
|
||||
qDebug() << "[WinDbg] DebugConnect hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "client=" << (void*)m_client;
|
||||
if (FAILED(hr) || !m_client) {
|
||||
@@ -86,7 +178,9 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
||||
else
|
||||
{
|
||||
// ── Local: create debug client for pid/dump ──
|
||||
hr = DebugCreate(IID_IDebugClient, (void**)&m_client);
|
||||
auto pfnCreate = getDebugCreate();
|
||||
if (!pfnCreate) { qWarning() << "[WinDbg] Debugging Tools required"; return; }
|
||||
hr = pfnCreate(IID_IDebugClient, (void**)&m_client);
|
||||
qDebug() << "[WinDbg] DebugCreate hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "client=" << (void*)m_client;
|
||||
if (FAILED(hr) || !m_client) {
|
||||
@@ -165,6 +259,10 @@ void WinDbgMemoryProvider::initInterfaces()
|
||||
qDebug() << "[WinDbg] IDebugDataSpaces hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "ptr=" << (void*)m_dataSpaces;
|
||||
|
||||
hr = m_client->QueryInterface(IID_IDebugDataSpaces2, (void**)&m_dataSpaces2);
|
||||
qDebug() << "[WinDbg] IDebugDataSpaces2 hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "ptr=" << (void*)m_dataSpaces2;
|
||||
|
||||
hr = m_client->QueryInterface(IID_IDebugControl, (void**)&m_control);
|
||||
qDebug() << "[WinDbg] IDebugControl hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "ptr=" << (void*)m_control;
|
||||
@@ -197,53 +295,28 @@ void WinDbgMemoryProvider::querySessionInfo()
|
||||
}
|
||||
}
|
||||
|
||||
if (m_symbols) {
|
||||
ULONG numModules = 0, numUnloaded = 0;
|
||||
hr = m_symbols->GetNumberModules(&numModules, &numUnloaded);
|
||||
qDebug() << "[WinDbg] GetNumberModules hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "loaded=" << numModules << "unloaded=" << numUnloaded;
|
||||
if (SUCCEEDED(hr) && numModules > 0) {
|
||||
char modName[256] = {};
|
||||
ULONG modSize = 0;
|
||||
hr = m_symbols->GetModuleNames(0, 0, nullptr, 0, nullptr,
|
||||
modName, sizeof(modName), &modSize,
|
||||
nullptr, 0, nullptr);
|
||||
if (SUCCEEDED(hr) && modSize > 0)
|
||||
m_name = QString::fromUtf8(modName);
|
||||
// Query effective processor type for pointer size detection
|
||||
if (m_control) {
|
||||
ULONG procType = 0;
|
||||
hr = m_control->GetEffectiveProcessorType(&procType);
|
||||
if (SUCCEEDED(hr)) {
|
||||
// IMAGE_FILE_MACHINE_I386 = 0x014C
|
||||
if (procType == 0x014C)
|
||||
m_pointerSize = 4;
|
||||
qDebug() << "[WinDbg] EffectiveProcessorType=" << Qt::hex << procType
|
||||
<< "pointerSize=" << m_pointerSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_name.isEmpty())
|
||||
m_name = m_isLive ? QStringLiteral("DbgEng (Live)") : QStringLiteral("DbgEng (Dump)");
|
||||
|
||||
if (m_symbols) {
|
||||
ULONG numModules = 0, numUnloaded = 0;
|
||||
hr = m_symbols->GetNumberModules(&numModules, &numUnloaded);
|
||||
if (SUCCEEDED(hr) && numModules > 0) {
|
||||
ULONG64 moduleBase = 0;
|
||||
hr = m_symbols->GetModuleByIndex(0, &moduleBase);
|
||||
qDebug() << "[WinDbg] Module 0 base=" << Qt::hex << moduleBase;
|
||||
if (SUCCEEDED(hr))
|
||||
m_base = moduleBase;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_base && m_dataSpaces) {
|
||||
uint8_t probe[2] = {};
|
||||
ULONG got = 0;
|
||||
hr = m_dataSpaces->ReadVirtual(m_base, probe, 2, &got);
|
||||
qDebug() << "[WinDbg] Probe read at" << Qt::hex << m_base
|
||||
<< "hr=" << (unsigned long)hr << "got=" << got
|
||||
<< "bytes:" << (int)probe[0] << (int)probe[1];
|
||||
if (FAILED(hr) || got == 0) {
|
||||
qWarning() << "[WinDbg] Probe read FAILED — cleaning up";
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// WinDbg provides access to the entire virtual address space.
|
||||
// Do NOT auto-select a module as base — let the user set their
|
||||
// own base address. m_base stays 0 so the controller won't
|
||||
// override tree.baseAddress.
|
||||
m_name = m_isLive ? QStringLiteral("WinDbg (Live)")
|
||||
: QStringLiteral("WinDbg (Dump)");
|
||||
|
||||
qDebug() << "[WinDbg] Ready. name=" << m_name
|
||||
<< "base=" << Qt::hex << m_base << "isLive=" << m_isLive;
|
||||
<< "isLive=" << m_isLive;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -260,6 +333,7 @@ WinDbgMemoryProvider::~WinDbgMemoryProvider()
|
||||
m_client->DetachProcesses();
|
||||
}
|
||||
cleanup();
|
||||
CoUninitialize();
|
||||
});
|
||||
} else {
|
||||
// Thread not running — clean up directly (best-effort)
|
||||
@@ -289,10 +363,11 @@ WinDbgMemoryProvider::~WinDbgMemoryProvider()
|
||||
void WinDbgMemoryProvider::cleanup()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
|
||||
if (m_control) { m_control->Release(); m_control = nullptr; }
|
||||
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
|
||||
if (m_client) { m_client->Release(); m_client = nullptr; }
|
||||
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
|
||||
if (m_control) { m_control->Release(); m_control = nullptr; }
|
||||
if (m_dataSpaces2) { m_dataSpaces2->Release(); m_dataSpaces2 = nullptr; }
|
||||
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
|
||||
if (m_client) { m_client->Release(); m_client = nullptr; }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -304,9 +379,19 @@ bool WinDbgMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
bool result = false;
|
||||
dispatchToOwner([&]() {
|
||||
ULONG bytesRead = 0;
|
||||
HRESULT hr = m_dataSpaces->ReadVirtual(m_base + addr, buf, (ULONG)len, &bytesRead);
|
||||
if (FAILED(hr) || (int)bytesRead < len)
|
||||
memset((char*)buf + bytesRead, 0, len - bytesRead);
|
||||
HRESULT hr = m_dataSpaces->ReadVirtual(addr, buf, (ULONG)len, &bytesRead);
|
||||
if (SUCCEEDED(hr) && (int)bytesRead >= len) {
|
||||
result = true;
|
||||
return;
|
||||
}
|
||||
// Partial or failed read — zero-fill remainder and log
|
||||
memset((char*)buf + bytesRead, 0, len - bytesRead);
|
||||
++m_readFailCount;
|
||||
if (m_readFailCount <= 5 || (m_readFailCount % 100) == 0)
|
||||
qDebug() << "[WinDbg] ReadVirtual FAILED addr=0x" << Qt::hex << addr
|
||||
<< "len=" << Qt::dec << len
|
||||
<< "hr=0x" << Qt::hex << (unsigned long)hr
|
||||
<< "got=" << Qt::dec << bytesRead;
|
||||
result = bytesRead > 0;
|
||||
});
|
||||
return result;
|
||||
@@ -324,7 +409,7 @@ bool WinDbgMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
bool result = false;
|
||||
dispatchToOwner([&]() {
|
||||
ULONG bytesWritten = 0;
|
||||
HRESULT hr = m_dataSpaces->WriteVirtual(m_base + addr, const_cast<void*>(buf),
|
||||
HRESULT hr = m_dataSpaces->WriteVirtual(addr, const_cast<void*>(buf),
|
||||
(ULONG)len, &bytesWritten);
|
||||
result = SUCCEEDED(hr) && bytesWritten == (ULONG)len;
|
||||
});
|
||||
@@ -364,7 +449,7 @@ QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
|
||||
char nameBuf[512] = {};
|
||||
ULONG nameSize = 0;
|
||||
ULONG64 displacement = 0;
|
||||
HRESULT hr = m_symbols->GetNameByOffset(m_base + addr, nameBuf, sizeof(nameBuf),
|
||||
HRESULT hr = m_symbols->GetNameByOffset(addr, nameBuf, sizeof(nameBuf),
|
||||
&nameSize, &displacement);
|
||||
if (SUCCEEDED(hr) && nameSize > 0) {
|
||||
result = QString::fromUtf8(nameBuf);
|
||||
@@ -379,6 +464,112 @@ QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
|
||||
#endif
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> WinDbgMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
#ifdef _WIN32
|
||||
if (!m_dataSpaces) return regions;
|
||||
|
||||
// Enumerate modules — used for tagging (user-mode) or as the primary
|
||||
// source of regions (kernel-mode, where QueryVirtual is unavailable).
|
||||
struct ModInfo { uint64_t base; uint64_t size; QString name; };
|
||||
QVector<ModInfo> modules;
|
||||
|
||||
if (m_symbols) {
|
||||
dispatchToOwner([&]() {
|
||||
ULONG loaded = 0, unloaded = 0;
|
||||
if (FAILED(m_symbols->GetNumberModules(&loaded, &unloaded)))
|
||||
return;
|
||||
for (ULONG i = 0; i < loaded; i++) {
|
||||
ULONG64 modBase = 0;
|
||||
if (FAILED(m_symbols->GetModuleByIndex(i, &modBase)))
|
||||
continue;
|
||||
DEBUG_MODULE_PARAMETERS params = {};
|
||||
if (FAILED(m_symbols->GetModuleParameters(1, &modBase, 0, ¶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
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -407,7 +598,7 @@ std::unique_ptr<rcx::Provider> WinDbgMemoryPlugin::createProvider(const QString&
|
||||
*errorMsg = QString("Failed to connect to debug server.\n\n"
|
||||
"Target: %1\n\n"
|
||||
"Make sure WinDbg is running with a matching .server command\n"
|
||||
"(e.g. .server tcp:port=5055) and the port/pipe is reachable.")
|
||||
"(e.g. .server tcp:port=5056) and the port/pipe is reachable.")
|
||||
.arg(target);
|
||||
else if (target.startsWith("pid:", Qt::CaseInsensitive))
|
||||
*errorMsg = QString("Failed to attach to process.\n\n"
|
||||
@@ -436,7 +627,7 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
{
|
||||
QDialog dlg(parent);
|
||||
dlg.setWindowTitle("WinDbg Settings");
|
||||
dlg.resize(460, 260);
|
||||
dlg.resize(480, 360);
|
||||
|
||||
QPalette dlgPal = qApp->palette();
|
||||
dlg.setPalette(dlgPal);
|
||||
@@ -444,15 +635,27 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
|
||||
auto* layout = new QVBoxLayout(&dlg);
|
||||
|
||||
QColor editBg = dlgPal.window().color().darker(115);
|
||||
QString editSS = QStringLiteral(
|
||||
"QLineEdit { background: %1; color: %2; border: 1px solid %3;"
|
||||
" border-radius: 3px; padding: 4px 6px; }")
|
||||
.arg(editBg.name(),
|
||||
dlgPal.color(QPalette::Text).name(),
|
||||
dlgPal.color(QPalette::Mid).name());
|
||||
|
||||
layout->addWidget(new QLabel(
|
||||
"Connect to a running WinDbg debug server.\n"
|
||||
"In WinDbg, run: .server tcp:port=5055"));
|
||||
"In WinDbg, run: .server tcp:port=5056\n\n"
|
||||
"Non-invasive debug and dump files only.\n"
|
||||
"Execution control (bp, g, t, p) is not supported.\n"
|
||||
"WinDbg Classic is recommended."));
|
||||
|
||||
layout->addSpacing(8);
|
||||
layout->addWidget(new QLabel("Connection string:"));
|
||||
auto* connEdit = new QLineEdit;
|
||||
connEdit->setPlaceholderText("tcp:Port=5055,Server=localhost");
|
||||
connEdit->setText("tcp:Port=5055,Server=localhost");
|
||||
connEdit->setPlaceholderText("tcp:Port=5056,Server=127.0.0.1");
|
||||
connEdit->setText("tcp:Port=5056,Server=127.0.0.1");
|
||||
connEdit->setStyleSheet(editSS);
|
||||
layout->addWidget(connEdit);
|
||||
|
||||
layout->addSpacing(4);
|
||||
@@ -476,8 +679,72 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
layout->addLayout(row);
|
||||
};
|
||||
|
||||
addExample(".server tcp:port=5055");
|
||||
addExample(".server tcp:port=5056");
|
||||
addExample(".server npipe:pipe=reclass");
|
||||
|
||||
// ── Debugger Tools status ──
|
||||
layout->addSpacing(8);
|
||||
#ifdef _WIN32
|
||||
bool found = dbgToolsFound();
|
||||
auto* toolsRow = new QHBoxLayout;
|
||||
auto* toolsLabel = new QLabel;
|
||||
if (found) {
|
||||
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
|
||||
QPalette tp = dlgPal;
|
||||
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||
toolsLabel->setPalette(tp);
|
||||
} else {
|
||||
toolsLabel->setText("Debugging Tools: not found");
|
||||
QPalette tp = dlgPal;
|
||||
tp.setColor(QPalette::WindowText, QColor(220, 120, 80));
|
||||
toolsLabel->setPalette(tp);
|
||||
}
|
||||
toolsLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
toolsRow->addWidget(toolsLabel, 1);
|
||||
|
||||
auto* browseBtn = new QPushButton("Browse...");
|
||||
browseBtn->setFixedWidth(70);
|
||||
browseBtn->setToolTip("Locate Debugging Tools for Windows directory (contains dbgeng.dll)");
|
||||
QObject::connect(browseBtn, &QPushButton::clicked, [&dlg, toolsLabel, &dlgPal]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(&dlg,
|
||||
"Locate Debugging Tools for Windows",
|
||||
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers");
|
||||
if (dir.isEmpty()) return;
|
||||
QString dllPath = dir + "/dbgeng.dll";
|
||||
if (!QFileInfo::exists(dllPath)) {
|
||||
QMessageBox::warning(&dlg, "Not Found",
|
||||
"dbgeng.dll was not found in that directory.\n"
|
||||
"Select the folder containing dbgeng.dll\n"
|
||||
"(e.g. Debuggers\\x64).");
|
||||
return;
|
||||
}
|
||||
QSettings settings;
|
||||
settings.setValue(kSettingsKey, dir);
|
||||
// Force reload on next use
|
||||
s_hDbgEng = nullptr;
|
||||
s_loadedDir.clear();
|
||||
if (dbgToolsFound()) {
|
||||
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
|
||||
QPalette tp = dlgPal;
|
||||
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||
toolsLabel->setPalette(tp);
|
||||
}
|
||||
});
|
||||
toolsRow->addWidget(browseBtn);
|
||||
layout->addLayout(toolsRow);
|
||||
|
||||
if (!found) {
|
||||
auto* note = new QLabel(
|
||||
"The system dbgeng.dll does not support remote connections.\n"
|
||||
"Install Debugging Tools for Windows or use Browse to locate them.");
|
||||
QPalette np = dlgPal;
|
||||
np.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||
note->setPalette(np);
|
||||
note->setWordWrap(true);
|
||||
layout->addWidget(note);
|
||||
}
|
||||
#endif
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
auto* btnLayout = new QHBoxLayout;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// Forward declarations for DbgEng COM interfaces
|
||||
struct IDebugClient;
|
||||
struct IDebugDataSpaces;
|
||||
struct IDebugDataSpaces2;
|
||||
struct IDebugControl;
|
||||
struct IDebugSymbols;
|
||||
|
||||
@@ -59,10 +60,11 @@ public:
|
||||
QString name() const override { return m_name; }
|
||||
QString kind() const override { return QStringLiteral("WinDbg"); }
|
||||
QString getSymbol(uint64_t addr) const override;
|
||||
QVector<rcx::MemoryRegion> enumerateRegions() const override;
|
||||
|
||||
bool isLive() const override { return m_isLive; }
|
||||
uint64_t base() const override { return m_base; }
|
||||
void setBase(uint64_t b) override { m_base = b; }
|
||||
int pointerSize() const override { return m_pointerSize; }
|
||||
|
||||
private:
|
||||
void initInterfaces(); // get IDebugDataSpaces/Control/Symbols from client
|
||||
@@ -74,16 +76,19 @@ private:
|
||||
template<typename Fn>
|
||||
void dispatchToOwner(Fn&& fn) const;
|
||||
|
||||
IDebugClient* m_client = nullptr;
|
||||
IDebugDataSpaces* m_dataSpaces = nullptr;
|
||||
IDebugControl* m_control = nullptr;
|
||||
IDebugSymbols* m_symbols = nullptr;
|
||||
IDebugClient* m_client = nullptr;
|
||||
IDebugDataSpaces* m_dataSpaces = nullptr;
|
||||
IDebugDataSpaces2* m_dataSpaces2 = nullptr;
|
||||
IDebugControl* m_control = nullptr;
|
||||
IDebugSymbols* m_symbols = nullptr;
|
||||
|
||||
QString m_name;
|
||||
uint64_t m_base = 0;
|
||||
bool m_isLive = false;
|
||||
bool m_writable = false;
|
||||
int m_pointerSize = 8;
|
||||
bool m_isRemote = false; // true when connected via DebugConnect (tcp/npipe)
|
||||
mutable int m_readFailCount = 0;
|
||||
|
||||
// Dedicated thread for DbgEng COM operations. The remote TCP/pipe
|
||||
// transport is thread-affine — all calls must happen on the thread
|
||||
|
||||
BIN
screenshot.png
|
Before Width: | Height: | Size: 63 KiB |
@@ -283,9 +283,10 @@ function Find-MinGWDirectory {
|
||||
$toolsDir = Join-Path $qtRoot "Tools"
|
||||
|
||||
if (Test-Path $toolsDir) {
|
||||
# Prefer GCC-based MinGW (has g++.exe); exclude llvm-mingw. Prefer 64-bit, then newest.
|
||||
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Name -match 'mingw'
|
||||
}
|
||||
$_.Name -match '^mingw\d+_\d+$'
|
||||
} | Sort-Object -Property @{ Expression = { if ($_.Name -match '_64$') { 1 } else { 0 } }; Descending = $true }, Name -Descending
|
||||
|
||||
foreach ($dir in $mingwToolDirs) {
|
||||
$testBin = Join-Path $dir.FullName "bin\g++.exe"
|
||||
|
||||
168
scripts/build_macos.sh
Executable file
@@ -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
|
||||
@@ -318,10 +318,10 @@ $qtRoot = Split-Path (Split-Path $selectedQtDir -Parent) -Parent
|
||||
$toolsDir = Join-Path $qtRoot "Tools"
|
||||
|
||||
if (Test-Path $toolsDir) {
|
||||
# Look for MinGW tools directory
|
||||
# Prefer GCC-based MinGW (has g++.exe); exclude llvm-mingw. Prefer 64-bit, then newest.
|
||||
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Name -match 'mingw'
|
||||
}
|
||||
$_.Name -match '^mingw\d+_\d+$'
|
||||
} | Sort-Object -Property @{ Expression = { if ($_.Name -match '_64$') { 1 } else { 0 } }; Descending = $true }, Name -Descending
|
||||
|
||||
foreach ($dir in $mingwToolDirs) {
|
||||
$testBin = Join-Path $dir.FullName "bin\g++.exe"
|
||||
|
||||
540
src/addressparser.cpp
Normal file
@@ -0,0 +1,540 @@
|
||||
#include "addressparser.h"
|
||||
|
||||
namespace rcx {
|
||||
|
||||
// ── Address Expression Parser ──────────────────────────────────────────
|
||||
//
|
||||
// Parses expressions like:
|
||||
// "7FF66CCE0000" → plain hex address
|
||||
// "0x100 + 0x200" → arithmetic on hex values
|
||||
// "<Program.exe> + 0xDE" → module base + offset
|
||||
// "[<Program.exe> + 0xDE] - AB" → dereference pointer, then subtract
|
||||
// "7ff6`6cce0000" → WinDbg-style backtick separator (stripped before parsing)
|
||||
// "base + e_lfanew" → C/C++ style identifier resolution
|
||||
// "0xFF & 0x0F" → bitwise AND
|
||||
// "1 << 4" → shift left
|
||||
//
|
||||
// Grammar (C operator precedence):
|
||||
//
|
||||
// bitwiseOr = bitwiseXor ('|' bitwiseXor)*
|
||||
// bitwiseXor = bitwiseAnd ('^' bitwiseAnd)*
|
||||
// bitwiseAnd = shift ('&' shift)*
|
||||
// shift = expr (('<<' | '>>') expr)*
|
||||
// expr = term (('+' | '-') term)*
|
||||
// term = unary (('*' | '/') unary)*
|
||||
// unary = '-' unary | '~' unary | atom
|
||||
// atom = '[' bitwiseOr ']' -- read pointer at address (dereference)
|
||||
// | '<' moduleName '>' -- resolve module base address
|
||||
// | '(' bitwiseOr ')' -- grouping
|
||||
// | identifier -- C/C++ name resolved via callback
|
||||
// | hexLiteral -- hex number, optional 0x prefix
|
||||
//
|
||||
// All numeric literals are hexadecimal (base 16).
|
||||
// Identifiers: [a-zA-Z_][a-zA-Z0-9_]* containing at least one non-hex char.
|
||||
// Module names with extensions (e.g. "client.dll") are scanned as one token.
|
||||
// Pure hex-digit words (e.g. "DEAD") are treated as hex literals.
|
||||
|
||||
class ExpressionParser {
|
||||
public:
|
||||
ExpressionParser(const QString& input, const AddressParserCallbacks* callbacks)
|
||||
: m_input(input), m_callbacks(callbacks) {}
|
||||
|
||||
AddressParseResult parse() {
|
||||
skipSpaces();
|
||||
if (atEnd())
|
||||
return error("empty expression");
|
||||
|
||||
uint64_t value = 0;
|
||||
if (!parseBitwiseOr(value))
|
||||
return error(m_error);
|
||||
|
||||
skipSpaces();
|
||||
if (!atEnd())
|
||||
return error(QStringLiteral("unexpected '%1'").arg(m_input[m_pos]));
|
||||
|
||||
return {true, value, {}, -1};
|
||||
}
|
||||
|
||||
private:
|
||||
const QString& m_input;
|
||||
const AddressParserCallbacks* m_callbacks;
|
||||
int m_pos = 0;
|
||||
QString m_error;
|
||||
int m_errorPos = 0;
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
bool atEnd() const { return m_pos >= m_input.size(); }
|
||||
|
||||
QChar peek() const { return atEnd() ? QChar('\0') : m_input[m_pos]; }
|
||||
|
||||
void advance() { m_pos++; }
|
||||
|
||||
void skipSpaces() {
|
||||
while (!atEnd() && m_input[m_pos].isSpace())
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
AddressParseResult error(const QString& msg) const {
|
||||
return {false, 0, msg, m_errorPos};
|
||||
}
|
||||
|
||||
bool fail(const QString& msg) {
|
||||
m_error = msg;
|
||||
m_errorPos = m_pos;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool expect(QChar ch) {
|
||||
skipSpaces();
|
||||
if (peek() != ch)
|
||||
return fail(QStringLiteral("expected '%1'").arg(ch));
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isHexDigit(QChar ch) {
|
||||
return (ch >= '0' && ch <= '9')
|
||||
|| (ch >= 'a' && ch <= 'f')
|
||||
|| (ch >= 'A' && ch <= 'F');
|
||||
}
|
||||
|
||||
static bool isIdentStart(QChar ch) {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
|
||||
}
|
||||
|
||||
static bool isIdentChar(QChar ch) {
|
||||
return isIdentStart(ch) || (ch >= '0' && ch <= '9');
|
||||
}
|
||||
|
||||
// ── Recursive descent parsing ──
|
||||
|
||||
// bitwiseOr = bitwiseXor ('|' bitwiseXor)*
|
||||
bool parseBitwiseOr(uint64_t& result) {
|
||||
if (!parseBitwiseXor(result))
|
||||
return false;
|
||||
for (;;) {
|
||||
skipSpaces();
|
||||
if (peek() != '|')
|
||||
break;
|
||||
advance();
|
||||
uint64_t rhs = 0;
|
||||
if (!parseBitwiseXor(rhs))
|
||||
return false;
|
||||
result |= rhs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// bitwiseXor = bitwiseAnd ('^' bitwiseAnd)*
|
||||
bool parseBitwiseXor(uint64_t& result) {
|
||||
if (!parseBitwiseAnd(result))
|
||||
return false;
|
||||
for (;;) {
|
||||
skipSpaces();
|
||||
if (peek() != '^')
|
||||
break;
|
||||
advance();
|
||||
uint64_t rhs = 0;
|
||||
if (!parseBitwiseAnd(rhs))
|
||||
return false;
|
||||
result ^= rhs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// bitwiseAnd = shift ('&' shift)*
|
||||
bool parseBitwiseAnd(uint64_t& result) {
|
||||
if (!parseShift(result))
|
||||
return false;
|
||||
for (;;) {
|
||||
skipSpaces();
|
||||
if (peek() != '&')
|
||||
break;
|
||||
advance();
|
||||
uint64_t rhs = 0;
|
||||
if (!parseShift(rhs))
|
||||
return false;
|
||||
result &= rhs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// shift = expr (('<<' | '>>') expr)*
|
||||
bool parseShift(uint64_t& result) {
|
||||
if (!parseExpression(result))
|
||||
return false;
|
||||
for (;;) {
|
||||
skipSpaces();
|
||||
QChar c = peek();
|
||||
if (c != '<' && c != '>')
|
||||
break;
|
||||
// Must be << or >> (not < or > alone)
|
||||
if (m_pos + 1 >= m_input.size() || m_input[m_pos + 1] != c)
|
||||
break;
|
||||
bool isLeft = (c == '<');
|
||||
advance(); advance(); // skip << or >>
|
||||
uint64_t rhs = 0;
|
||||
if (!parseExpression(rhs))
|
||||
return false;
|
||||
result = isLeft ? (result << rhs) : (result >> rhs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// expr = term (('+' | '-') term)*
|
||||
bool parseExpression(uint64_t& result) {
|
||||
if (!parseTerm(result))
|
||||
return false;
|
||||
|
||||
for (;;) {
|
||||
skipSpaces();
|
||||
QChar op = peek();
|
||||
if (op != '+' && op != '-')
|
||||
break;
|
||||
advance();
|
||||
|
||||
uint64_t rhs = 0;
|
||||
if (!parseTerm(rhs))
|
||||
return false;
|
||||
|
||||
result = (op == '+') ? result + rhs : result - rhs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// term = unary (('*' | '/') unary)*
|
||||
bool parseTerm(uint64_t& result) {
|
||||
if (!parseUnary(result))
|
||||
return false;
|
||||
|
||||
for (;;) {
|
||||
skipSpaces();
|
||||
QChar op = peek();
|
||||
if (op != '*' && op != '/')
|
||||
break;
|
||||
advance();
|
||||
|
||||
uint64_t rhs = 0;
|
||||
if (!parseUnary(rhs))
|
||||
return false;
|
||||
|
||||
if (op == '*') {
|
||||
result *= rhs;
|
||||
} else {
|
||||
if (rhs == 0)
|
||||
return fail("division by zero");
|
||||
result /= rhs;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// unary = '-' unary | '~' unary | atom
|
||||
bool parseUnary(uint64_t& result) {
|
||||
skipSpaces();
|
||||
if (peek() == '-') {
|
||||
advance();
|
||||
uint64_t inner = 0;
|
||||
if (!parseUnary(inner))
|
||||
return false;
|
||||
result = static_cast<uint64_t>(-static_cast<int64_t>(inner));
|
||||
return true;
|
||||
}
|
||||
if (peek() == '~') {
|
||||
advance();
|
||||
uint64_t inner = 0;
|
||||
if (!parseUnary(inner))
|
||||
return false;
|
||||
result = ~inner;
|
||||
return true;
|
||||
}
|
||||
return parseAtom(result);
|
||||
}
|
||||
|
||||
// atom = '[' bitwiseOr ']' | '<' name '>' | '(' bitwiseOr ')' | identifier | hexLiteral
|
||||
bool parseAtom(uint64_t& result) {
|
||||
skipSpaces();
|
||||
if (atEnd())
|
||||
return fail("unexpected end of expression");
|
||||
|
||||
QChar ch = peek();
|
||||
|
||||
if (ch == '[') return parseDereference(result);
|
||||
if (ch == '<') return parseModuleName(result);
|
||||
if (ch == '(') return parseGrouping(result);
|
||||
|
||||
// Try identifier before hex — identifiers start with [a-zA-Z_]
|
||||
if (isIdentStart(ch))
|
||||
return parseIdentifierOrHex(result);
|
||||
|
||||
return parseHexNumber(result);
|
||||
}
|
||||
|
||||
// Identifier or hex literal disambiguation.
|
||||
// Scan [a-zA-Z_][a-zA-Z0-9_]*. If it contains any non-hex char → identifier.
|
||||
// Otherwise → backtrack and parse as hex number.
|
||||
// WinDbg-style "module!symbol" is scanned as a single identifier token.
|
||||
// If the identifier is followed by '(', try to parse as a built-in function call.
|
||||
bool parseIdentifierOrHex(uint64_t& result) {
|
||||
int start = m_pos;
|
||||
bool hasNonHex = false;
|
||||
|
||||
// Scan full token, including "module!symbol" as one token
|
||||
while (!atEnd() && isIdentChar(peek())) {
|
||||
if (!isHexDigit(peek()))
|
||||
hasNonHex = true;
|
||||
advance();
|
||||
}
|
||||
// Handle module.dll / module.exe / module.sys extensions
|
||||
// e.g. "client.dll + 0xFF" should parse "client.dll" as one token
|
||||
if (!atEnd() && peek() == '.' && m_pos > start) {
|
||||
int dotPos = m_pos;
|
||||
advance(); // skip '.'
|
||||
int extStart = m_pos;
|
||||
while (!atEnd() && isIdentChar(peek()))
|
||||
advance();
|
||||
if (m_pos > extStart) {
|
||||
hasNonHex = true; // '.' makes it definitively an identifier
|
||||
} else {
|
||||
m_pos = dotPos; // backtrack — '.' at end isn't an extension
|
||||
}
|
||||
}
|
||||
// If we hit '!' and the next char is an identifier start, extend the token
|
||||
// to include the second part (WinDbg module!symbol syntax)
|
||||
if (!atEnd() && peek() == '!' && m_pos > start) {
|
||||
int bangPos = m_pos;
|
||||
advance(); // skip '!'
|
||||
if (!atEnd() && isIdentStart(peek())) {
|
||||
hasNonHex = true;
|
||||
while (!atEnd() && isIdentChar(peek())) {
|
||||
advance();
|
||||
}
|
||||
} else {
|
||||
m_pos = bangPos; // backtrack — '!' at end isn't module!symbol
|
||||
}
|
||||
}
|
||||
|
||||
QString token = m_input.mid(start, m_pos - start);
|
||||
|
||||
if (!hasNonHex) {
|
||||
// Pure hex digits (e.g. "DEAD") — backtrack, parse as hex
|
||||
m_pos = start;
|
||||
return parseHexNumber(result);
|
||||
}
|
||||
|
||||
// Check for function call syntax: identifier '(' args ')'
|
||||
skipSpaces();
|
||||
if (peek() == '(')
|
||||
return parseFunctionCall(token, result);
|
||||
|
||||
// It's an identifier — resolve via callback
|
||||
if (!m_callbacks || !m_callbacks->resolveIdentifier) {
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
result = m_callbacks->resolveIdentifier(token, &ok);
|
||||
if (!ok)
|
||||
return fail(QStringLiteral("unknown identifier '%1'").arg(token));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Built-in function call: vtop(pid, va), cr3(pid), phys(addr)
|
||||
bool parseFunctionCall(const QString& name, uint64_t& result) {
|
||||
advance(); // skip '('
|
||||
|
||||
if (name == QStringLiteral("vtop")) {
|
||||
// vtop(pid, virtualAddress) → physical address
|
||||
uint64_t pid = 0;
|
||||
if (!parseBitwiseOr(pid)) return false;
|
||||
skipSpaces();
|
||||
if (peek() != ',')
|
||||
return fail("vtop() requires 2 arguments: vtop(pid, va)");
|
||||
advance(); // skip ','
|
||||
uint64_t va = 0;
|
||||
if (!parseBitwiseOr(va)) return false;
|
||||
if (!expect(')')) return false;
|
||||
|
||||
if (!m_callbacks || !m_callbacks->vtop) {
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
bool ok = false;
|
||||
result = m_callbacks->vtop((uint32_t)pid, va, &ok);
|
||||
if (!ok)
|
||||
return fail(QStringLiteral("vtop(0x%1, 0x%2) failed")
|
||||
.arg(pid, 0, 16).arg(va, 0, 16));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == QStringLiteral("cr3")) {
|
||||
// cr3(pid) → CR3 value
|
||||
uint64_t pid = 0;
|
||||
if (!parseBitwiseOr(pid)) return false;
|
||||
if (!expect(')')) return false;
|
||||
|
||||
if (!m_callbacks || !m_callbacks->cr3) {
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
bool ok = false;
|
||||
result = m_callbacks->cr3((uint32_t)pid, &ok);
|
||||
if (!ok)
|
||||
return fail(QStringLiteral("cr3(%1) failed").arg(pid));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == QStringLiteral("phys")) {
|
||||
// phys(addr) → read 8 bytes from physical address
|
||||
uint64_t addr = 0;
|
||||
if (!parseBitwiseOr(addr)) return false;
|
||||
if (!expect(')')) return false;
|
||||
|
||||
if (!m_callbacks || !m_callbacks->physRead) {
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
bool ok = false;
|
||||
result = m_callbacks->physRead(addr, &ok);
|
||||
if (!ok)
|
||||
return fail(QStringLiteral("phys(0x%1) failed").arg(addr, 0, 16));
|
||||
return true;
|
||||
}
|
||||
|
||||
return fail(QStringLiteral("unknown function '%1'").arg(name));
|
||||
}
|
||||
|
||||
// '[' bitwiseOr ']' — read the pointer value at the computed address
|
||||
bool parseDereference(uint64_t& result) {
|
||||
advance(); // skip '['
|
||||
|
||||
uint64_t address = 0;
|
||||
if (!parseBitwiseOr(address))
|
||||
return false;
|
||||
if (!expect(']'))
|
||||
return false;
|
||||
|
||||
// Without a callback, just return 0 (syntax-check mode)
|
||||
if (!m_callbacks || !m_callbacks->readPointer) {
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
result = m_callbacks->readPointer(address, &ok);
|
||||
if (!ok)
|
||||
return fail(QStringLiteral("failed to read memory at 0x%1").arg(address, 0, 16));
|
||||
return true;
|
||||
}
|
||||
|
||||
// '<' moduleName '>' — resolve a module's base address (e.g. <Program.exe>)
|
||||
bool parseModuleName(uint64_t& result) {
|
||||
advance(); // skip '<'
|
||||
|
||||
int nameStart = m_pos;
|
||||
while (!atEnd() && peek() != '>')
|
||||
advance();
|
||||
if (atEnd())
|
||||
return fail("expected '>'");
|
||||
|
||||
QString name = m_input.mid(nameStart, m_pos - nameStart).trimmed();
|
||||
advance(); // skip '>'
|
||||
|
||||
if (name.isEmpty())
|
||||
return fail("empty module name");
|
||||
|
||||
// Without a callback, just return 0 (syntax-check mode)
|
||||
if (!m_callbacks || !m_callbacks->resolveModule) {
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
result = m_callbacks->resolveModule(name, &ok);
|
||||
if (!ok)
|
||||
return fail(QStringLiteral("module '%1' not found").arg(name));
|
||||
return true;
|
||||
}
|
||||
|
||||
// '(' bitwiseOr ')' — parenthesized sub-expression for grouping
|
||||
bool parseGrouping(uint64_t& result) {
|
||||
advance(); // skip '('
|
||||
if (!parseBitwiseOr(result))
|
||||
return false;
|
||||
return expect(')');
|
||||
}
|
||||
|
||||
// Hex number with optional "0x" prefix. All literals are base-16.
|
||||
bool parseHexNumber(uint64_t& result) {
|
||||
skipSpaces();
|
||||
if (atEnd())
|
||||
return fail("unexpected end of expression");
|
||||
|
||||
int start = m_pos;
|
||||
|
||||
// Skip optional 0x/0X prefix
|
||||
if (m_pos + 1 < m_input.size()
|
||||
&& m_input[m_pos] == '0'
|
||||
&& (m_input[m_pos + 1] == 'x' || m_input[m_pos + 1] == 'X'))
|
||||
m_pos += 2;
|
||||
|
||||
// Consume hex digits
|
||||
int digitsStart = m_pos;
|
||||
while (!atEnd() && isHexDigit(peek()))
|
||||
advance();
|
||||
|
||||
if (m_pos == digitsStart) {
|
||||
m_errorPos = start;
|
||||
return fail("expected hex number");
|
||||
}
|
||||
|
||||
QString digits = m_input.mid(digitsStart, m_pos - digitsStart);
|
||||
bool ok = false;
|
||||
result = digits.toULongLong(&ok, 16);
|
||||
if (!ok) {
|
||||
m_errorPos = start;
|
||||
return fail("invalid hex number");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// ── Public API ─────────────────────────────────────────────────────────
|
||||
|
||||
AddressParseResult AddressParser::evaluate(const QString& formula, int ptrSize,
|
||||
const AddressParserCallbacks* cb)
|
||||
{
|
||||
// ptrSize is used by the caller to configure the readPointer callback;
|
||||
// the parser itself doesn't need it directly.
|
||||
Q_UNUSED(ptrSize);
|
||||
|
||||
// WinDbg displays 64-bit addresses with backtick separators for readability,
|
||||
// e.g. "00007ff6`1a2b3c4d". Strip them so users can paste directly.
|
||||
// Also remove ' in case user uses it
|
||||
QString cleaned = formula;
|
||||
cleaned.remove('`');
|
||||
cleaned.remove('\'');
|
||||
|
||||
ExpressionParser parser(cleaned, cb);
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
QString AddressParser::validate(const QString& formula)
|
||||
{
|
||||
QString cleaned = formula;
|
||||
cleaned.remove('`');
|
||||
cleaned.remove('\'');
|
||||
cleaned = cleaned.trimmed();
|
||||
if (cleaned.isEmpty())
|
||||
return QStringLiteral("empty");
|
||||
|
||||
// Parse with no callbacks — modules, dereferences, identifiers succeed but return 0.
|
||||
// This checks syntax only.
|
||||
ExpressionParser parser(cleaned, nullptr);
|
||||
auto result = parser.parse();
|
||||
return result.ok ? QString() : result.error;
|
||||
}
|
||||
|
||||
} // namespace rcx
|
||||
33
src/addressparser.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
struct AddressParseResult {
|
||||
bool ok;
|
||||
uint64_t value;
|
||||
QString error;
|
||||
int errorPos;
|
||||
};
|
||||
|
||||
struct AddressParserCallbacks {
|
||||
std::function<uint64_t(const QString& name, bool* ok)> resolveModule;
|
||||
std::function<uint64_t(uint64_t addr, bool* ok)> readPointer;
|
||||
std::function<uint64_t(const QString& name, bool* ok)> resolveIdentifier;
|
||||
|
||||
// Kernel paging functions (optional — only wired when kernel provider active)
|
||||
std::function<uint64_t(uint32_t pid, uint64_t va, bool* ok)> vtop;
|
||||
std::function<uint64_t(uint32_t pid, bool* ok)> cr3;
|
||||
std::function<uint64_t(uint64_t physAddr, bool* ok)> physRead;
|
||||
};
|
||||
|
||||
class AddressParser {
|
||||
public:
|
||||
static AddressParseResult evaluate(const QString& formula, int ptrSize = 8,
|
||||
const AddressParserCallbacks* cb = nullptr);
|
||||
static QString validate(const QString& formula);
|
||||
};
|
||||
|
||||
} // namespace rcx
|
||||
823
src/compose.cpp
2916
src/controller.cpp
@@ -40,7 +40,10 @@ public:
|
||||
return m ? QString::fromLatin1(m->typeName) : QStringLiteral("???");
|
||||
}
|
||||
|
||||
ComposeResult compose(uint64_t viewRootId = 0) const;
|
||||
ComposeResult compose(uint64_t viewRootId = 0, bool compactColumns = false,
|
||||
bool treeLines = false, bool braceWrap = false,
|
||||
bool typeHints = false,
|
||||
SymbolLookupFn symbolLookup = {}) const;
|
||||
bool save(const QString& path);
|
||||
bool load(const QString& path);
|
||||
void loadData(const QString& binaryPath);
|
||||
@@ -70,6 +73,7 @@ struct SavedSourceEntry {
|
||||
QString filePath; // for File sources
|
||||
QString providerTarget; // for plugin providers (e.g. "pid:name")
|
||||
uint64_t baseAddress = 0;
|
||||
QString baseAddressFormula;
|
||||
};
|
||||
|
||||
// ── Controller ──
|
||||
@@ -85,20 +89,32 @@ public:
|
||||
void removeSplitEditor(RcxEditor* editor);
|
||||
QList<RcxEditor*> editors() const { return m_editors; }
|
||||
|
||||
void convertRootKeyword(const QString& newKeyword);
|
||||
void changeNodeKind(int nodeIdx, NodeKind newKind);
|
||||
void renameNode(int nodeIdx, const QString& newName);
|
||||
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
||||
void insertNodeAbove(int beforeIdx, NodeKind kind, const QString& name);
|
||||
void removeNode(int nodeIdx);
|
||||
void toggleCollapse(int nodeIdx);
|
||||
void materializeRefChildren(int nodeIdx);
|
||||
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
|
||||
void setNodeValue(int nodeIdx, int subLine, const QString& text,
|
||||
bool isAscii = false, uint64_t resolvedAddr = 0);
|
||||
void duplicateNode(int nodeIdx);
|
||||
void convertToTypedPointer(uint64_t nodeId);
|
||||
void splitHexNode(uint64_t nodeId);
|
||||
void toggleBitfieldBit(uint64_t nodeId, int memberIdx);
|
||||
void editBitfieldValue(uint64_t nodeId, int memberIdx);
|
||||
void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);
|
||||
void batchRemoveNodes(const QVector<int>& nodeIndices);
|
||||
void batchChangeKind(const QVector<int>& nodeIndices, NodeKind newKind);
|
||||
void deleteRootStruct(uint64_t structId);
|
||||
void groupIntoUnion(const QSet<uint64_t>& nodeIds);
|
||||
void dissolveUnion(uint64_t unionId);
|
||||
|
||||
void applyCommand(const Command& cmd, bool isUndo);
|
||||
void refresh();
|
||||
void applyTypePopupResult(TypePopupMode mode, int nodeIdx, const TypeEntry& entry, const QString& fullText);
|
||||
uint64_t findOrCreateStructByName(const QString& typeName);
|
||||
|
||||
// Selection
|
||||
void handleNodeClick(RcxEditor* source, int line, uint64_t nodeId,
|
||||
@@ -113,6 +129,13 @@ public:
|
||||
|
||||
RcxDocument* document() const { return m_doc; }
|
||||
void setEditorFont(const QString& fontName);
|
||||
void setRefreshInterval(int ms);
|
||||
void setCompactColumns(bool v);
|
||||
void setTreeLines(bool v);
|
||||
void setBraceWrap(bool v);
|
||||
void setTypeHints(bool v);
|
||||
bool typeHints() const { return m_typeHints; }
|
||||
void resetProvider();
|
||||
|
||||
// MCP bridge accessors
|
||||
void setSuppressRefresh(bool v) { m_suppressRefresh = v; }
|
||||
@@ -120,10 +143,29 @@ public:
|
||||
const QVector<SavedSourceEntry>& savedSources() const { return m_savedSources; }
|
||||
int activeSourceIndex() const { return m_activeSourceIdx; }
|
||||
void switchSource(int idx) { switchToSavedSource(idx); }
|
||||
void clearSources();
|
||||
void selectSource(const QString& text);
|
||||
void copySavedSources(const QVector<SavedSourceEntry>& sources, int activeIdx);
|
||||
|
||||
// Value tracking toggle (per-tab, off by default)
|
||||
bool trackValues() const { return m_trackValues; }
|
||||
void setTrackValues(bool on);
|
||||
void resetChangeTracking();
|
||||
|
||||
// Cross-tab type visibility: point at the project's full document list
|
||||
void setProjectDocuments(QVector<RcxDocument*>* docs) { m_projectDocs = docs; }
|
||||
|
||||
// Test accessors
|
||||
const QHash<uint64_t, ValueHistory>& valueHistory() const { return m_valueHistory; }
|
||||
const ComposeResult& lastResult() const { return m_lastResult; }
|
||||
int dataExtent() const { return computeDataExtent(); }
|
||||
|
||||
signals:
|
||||
void nodeSelected(int nodeIdx);
|
||||
void selectionChanged(int count);
|
||||
void contextMenuAboutToShow(QMenu* menu, int line);
|
||||
void requestOpenProviderTab(const QString& pluginId, const QString& target,
|
||||
const QString& title);
|
||||
|
||||
private:
|
||||
RcxDocument* m_doc;
|
||||
@@ -132,6 +174,10 @@ private:
|
||||
QSet<uint64_t> m_selIds;
|
||||
int m_anchorLine = -1;
|
||||
bool m_suppressRefresh = false;
|
||||
bool m_compactColumns = false;
|
||||
bool m_treeLines = false;
|
||||
bool m_braceWrap = false;
|
||||
bool m_typeHints = false;
|
||||
uint64_t m_viewRootId = 0;
|
||||
|
||||
// ── Saved sources for quick-switch ──
|
||||
@@ -140,6 +186,7 @@ private:
|
||||
|
||||
// ── Cached type selector popup (avoids ~350ms cold-start on first show) ──
|
||||
QPointer<TypeSelectorPopup> m_cachedPopup;
|
||||
int m_typePopupGen = 0; // generation counter for deferred content loading
|
||||
|
||||
// ── Auto-refresh state ──
|
||||
using PageMap = QHash<uint64_t, QByteArray>;
|
||||
@@ -148,18 +195,22 @@ private:
|
||||
std::unique_ptr<SnapshotProvider> m_snapshotProv;
|
||||
PageMap m_prevPages;
|
||||
QSet<int64_t> m_changedOffsets;
|
||||
QHash<uint64_t, ValueHistory> m_valueHistory;
|
||||
QHash<uint64_t, uint64_t> m_lastValueAddr; // nodeId → last offsetAddr used for value recording
|
||||
bool m_trackValues = true;
|
||||
int m_valueTrackCooldown = 0; // suppress value recording for N refresh cycles after clear
|
||||
uint64_t m_refreshGen = 0;
|
||||
uint64_t m_readGen = 0;
|
||||
bool m_readInFlight = false;
|
||||
|
||||
QVector<RcxDocument*>* m_projectDocs = nullptr;
|
||||
|
||||
void connectEditor(RcxEditor* editor);
|
||||
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
||||
void updateCommandRow();
|
||||
void performRealignment(uint64_t structId, int targetAlign);
|
||||
void switchToSavedSource(int idx);
|
||||
void pushSavedSourcesToEditors();
|
||||
void showTypePopup(RcxEditor* editor, TypePopupMode mode, int nodeIdx, QPoint globalPos);
|
||||
void applyTypePopupResult(TypePopupMode mode, int nodeIdx, const TypeEntry& entry, const QString& fullText);
|
||||
TypeSelectorPopup* ensurePopup(RcxEditor* editor);
|
||||
|
||||
// ── Auto-refresh methods ──
|
||||
|
||||
378
src/core.h
@@ -8,8 +8,10 @@
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "providers/provider.h"
|
||||
#include "providers/buffer_provider.h"
|
||||
@@ -25,6 +27,7 @@ enum class NodeKind : uint8_t {
|
||||
UInt8, UInt16, UInt32, UInt64,
|
||||
Float, Double, Bool,
|
||||
Pointer32, Pointer64,
|
||||
FuncPtr32, FuncPtr64,
|
||||
Vec2, Vec3, Vec4, Mat4x4,
|
||||
UTF8, UTF16,
|
||||
Struct, Array
|
||||
@@ -77,12 +80,14 @@ inline constexpr KindMeta kKindMeta[] = {
|
||||
{NodeKind::Bool, "Bool", "bool", 1, 1, 1, KF_None},
|
||||
{NodeKind::Pointer32, "Pointer32", "ptr32", 4, 1, 4, KF_None},
|
||||
{NodeKind::Pointer64, "Pointer64", "ptr64", 8, 1, 8, KF_None},
|
||||
{NodeKind::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::Vec3, "Vec3", "vec3", 12, 1, 4, KF_Vector},
|
||||
{NodeKind::Vec4, "Vec4", "vec4", 16, 1, 4, KF_Vector},
|
||||
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
|
||||
{NodeKind::UTF8, "UTF8", "char[]", 1, 1, 1, KF_String},
|
||||
{NodeKind::UTF16, "UTF16", "wchar_t[]", 2, 1, 2, KF_String},
|
||||
{NodeKind::UTF8, "UTF8", "str", 1, 1, 1, KF_String},
|
||||
{NodeKind::UTF16, "UTF16", "wstr", 2, 1, 2, KF_String},
|
||||
{NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container},
|
||||
{NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container},
|
||||
};
|
||||
@@ -123,29 +128,36 @@ inline constexpr uint32_t flagsFor(NodeKind k) {
|
||||
const auto* m = kindMeta(k);
|
||||
return m ? m->flags : 0;
|
||||
}
|
||||
inline constexpr bool isHexPreview(NodeKind k) {
|
||||
return flagsFor(k) & KF_HexPreview;
|
||||
}
|
||||
inline constexpr bool isHexNode(NodeKind k) {
|
||||
return k >= NodeKind::Hex8 && k <= NodeKind::Hex64;
|
||||
}
|
||||
inline constexpr bool isHexPreview(NodeKind k) {
|
||||
return isHexNode(k);
|
||||
}
|
||||
inline constexpr bool isVectorKind(NodeKind k) {
|
||||
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;
|
||||
out.reserve(std::size(kKindMeta));
|
||||
for (const auto& m : kKindMeta) {
|
||||
QString t = QString::fromLatin1(m.typeName);
|
||||
if (stripBrackets) t.remove(QStringLiteral("[]"));
|
||||
out << t;
|
||||
}
|
||||
out.sort(Qt::CaseInsensitive);
|
||||
out.removeDuplicates();
|
||||
for (const auto& m : kKindMeta)
|
||||
out << QString::fromLatin1(m.typeName);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -161,6 +173,15 @@ enum Marker : int {
|
||||
M_SELECTED = 7,
|
||||
M_CMD_ROW = 8,
|
||||
M_ACCENT = 9,
|
||||
M_FOCUS = 10, // Presentation mode: AI focus glow
|
||||
};
|
||||
|
||||
// ── 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 ──
|
||||
@@ -173,12 +194,18 @@ struct Node {
|
||||
QString classKeyword; // "struct", "class", or "enum" (empty = "struct")
|
||||
uint64_t parentId = 0; // 0 = root (no parent)
|
||||
int offset = 0;
|
||||
bool isStatic = false; // static field — excluded from struct layout
|
||||
QString offsetExpr; // C/C++ expression → absolute address (static fields only)
|
||||
bool isRelative = false; // Pointer: target = base + value (RVA) instead of absolute
|
||||
int arrayLen = 1; // Array: element count
|
||||
int strLen = 64;
|
||||
bool collapsed = false;
|
||||
bool collapsed = true;
|
||||
uint64_t refId = 0; // Pointer32/64: id of Struct to expand at *ptr
|
||||
NodeKind elementKind = NodeKind::UInt8; // Array: element type
|
||||
NodeKind elementKind = NodeKind::UInt8; // Array: element type; Pointer with ptrDepth>0: target type
|
||||
int ptrDepth = 0; // Pointer: 0=struct/void ptr, 1=primitive*, 2=primitive**
|
||||
int viewIndex = 0; // Array: current view offset (transient)
|
||||
QVector<QPair<QString, int64_t>> enumMembers; // Enum: name→value pairs
|
||||
QVector<BitfieldMember> bitfieldMembers; // Bitfield: per-bit member definitions
|
||||
|
||||
// Note: Returns 0 for Array-of-Struct/Array. Use tree.structSpan() for accurate size.
|
||||
int byteSize() const {
|
||||
@@ -190,6 +217,12 @@ struct Node {
|
||||
if (elemSz <= 0) return 0;
|
||||
return qMin(arrayLen, INT_MAX / elemSz) * elemSz;
|
||||
}
|
||||
case NodeKind::Struct:
|
||||
if (classKeyword == QStringLiteral("bitfield")) {
|
||||
int sz = sizeForKind(elementKind);
|
||||
return sz > 0 ? sz : 4;
|
||||
}
|
||||
return 0;
|
||||
default: return sizeForKind(kind);
|
||||
}
|
||||
}
|
||||
@@ -205,11 +238,40 @@ struct Node {
|
||||
o["classKeyword"] = classKeyword;
|
||||
o["parentId"] = QString::number(parentId);
|
||||
o["offset"] = offset;
|
||||
if (isStatic)
|
||||
o["isStatic"] = true;
|
||||
if (!offsetExpr.isEmpty())
|
||||
o["offsetExpr"] = offsetExpr;
|
||||
if (isRelative)
|
||||
o["isRelative"] = true;
|
||||
o["arrayLen"] = arrayLen;
|
||||
o["strLen"] = strLen;
|
||||
o["collapsed"] = collapsed;
|
||||
o["refId"] = QString::number(refId);
|
||||
o["elementKind"] = kindToString(elementKind);
|
||||
if (ptrDepth > 0)
|
||||
o["ptrDepth"] = ptrDepth;
|
||||
if (!enumMembers.isEmpty()) {
|
||||
QJsonArray arr;
|
||||
for (const auto& m : enumMembers) {
|
||||
QJsonObject em;
|
||||
em["name"] = m.first;
|
||||
em["value"] = QString::number(m.second);
|
||||
arr.append(em);
|
||||
}
|
||||
o["enumMembers"] = arr;
|
||||
}
|
||||
if (!bitfieldMembers.isEmpty()) {
|
||||
QJsonArray arr;
|
||||
for (const auto& m : bitfieldMembers) {
|
||||
QJsonObject bm;
|
||||
bm["name"] = m.name;
|
||||
bm["bitOffset"] = m.bitOffset;
|
||||
bm["bitWidth"] = m.bitWidth;
|
||||
arr.append(bm);
|
||||
}
|
||||
o["bitfieldMembers"] = arr;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
static Node fromJson(const QJsonObject& o) {
|
||||
@@ -221,11 +283,34 @@ struct Node {
|
||||
n.classKeyword = o["classKeyword"].toString();
|
||||
n.parentId = o["parentId"].toString("0").toULongLong();
|
||||
n.offset = o["offset"].toInt(0);
|
||||
n.isStatic = o["isStatic"].toBool(o["isHelper"].toBool(false));
|
||||
n.offsetExpr = o["offsetExpr"].toString();
|
||||
n.isRelative = o["isRelative"].toBool(false);
|
||||
n.arrayLen = qBound(1, o["arrayLen"].toInt(1), 1000000);
|
||||
n.strLen = qBound(1, o["strLen"].toInt(64), 1000000);
|
||||
n.collapsed = o["collapsed"].toBool(false);
|
||||
n.collapsed = true; // Always load collapsed; user expands as needed
|
||||
n.refId = o["refId"].toString("0").toULongLong();
|
||||
n.elementKind = kindFromString(o["elementKind"].toString("UInt8"));
|
||||
n.ptrDepth = qBound(0, o["ptrDepth"].toInt(0), 2);
|
||||
if (o.contains("enumMembers")) {
|
||||
QJsonArray arr = o["enumMembers"].toArray();
|
||||
for (const auto& v : arr) {
|
||||
QJsonObject em = v.toObject();
|
||||
n.enumMembers.emplaceBack(em["name"].toString(),
|
||||
em["value"].toString("0").toLongLong());
|
||||
}
|
||||
}
|
||||
if (o.contains("bitfieldMembers")) {
|
||||
QJsonArray arr = o["bitfieldMembers"].toArray();
|
||||
for (const auto& v : arr) {
|
||||
QJsonObject bm = v.toObject();
|
||||
BitfieldMember m;
|
||||
m.name = bm["name"].toString();
|
||||
m.bitOffset = (uint8_t)qBound(0, bm["bitOffset"].toInt(0), 255);
|
||||
m.bitWidth = (uint8_t)qBound(1, bm["bitWidth"].toInt(1), 64);
|
||||
n.bitfieldMembers.append(m);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -247,8 +332,11 @@ struct Node {
|
||||
struct NodeTree {
|
||||
QVector<Node> nodes;
|
||||
uint64_t baseAddress = 0x00400000;
|
||||
QString baseAddressFormula; // e.g. "<ReClass.exe> + 0x100"
|
||||
int pointerSize = 8; // 4 for 32-bit targets, 8 for 64-bit
|
||||
uint64_t m_nextId = 1;
|
||||
mutable QHash<uint64_t, int> m_idCache;
|
||||
mutable QHash<uint64_t, QVector<int>> m_childCache;
|
||||
|
||||
int addNode(const Node& n) {
|
||||
Node copy = n;
|
||||
@@ -258,13 +346,15 @@ struct NodeTree {
|
||||
nodes.append(copy);
|
||||
if (!m_idCache.isEmpty())
|
||||
m_idCache[copy.id] = idx;
|
||||
if (!m_childCache.isEmpty())
|
||||
m_childCache[copy.parentId].append(idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Reserve a unique ID atomically (for use before pushing undo commands)
|
||||
uint64_t reserveId() { return m_nextId++; }
|
||||
|
||||
void invalidateIdCache() const { m_idCache.clear(); }
|
||||
void invalidateIdCache() const { m_idCache.clear(); m_childCache.clear(); }
|
||||
|
||||
int indexOfId(uint64_t id) const {
|
||||
if (m_idCache.isEmpty() && !nodes.isEmpty()) {
|
||||
@@ -275,11 +365,11 @@ struct NodeTree {
|
||||
}
|
||||
|
||||
QVector<int> childrenOf(uint64_t parentId) const {
|
||||
QVector<int> result;
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i].parentId == parentId) result.append(i);
|
||||
if (m_childCache.isEmpty() && !nodes.isEmpty()) {
|
||||
for (int i = 0; i < nodes.size(); i++)
|
||||
m_childCache[nodes[i].parentId].append(i);
|
||||
}
|
||||
return result;
|
||||
return m_childCache.value(parentId);
|
||||
}
|
||||
|
||||
// Collect node + all descendants (iterative, cycle-safe)
|
||||
@@ -360,10 +450,11 @@ struct NodeTree {
|
||||
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
|
||||
for (int ci : kids) {
|
||||
const Node& c = nodes[ci];
|
||||
if (c.isStatic) continue; // static fields don't affect struct size
|
||||
int sz = (c.kind == NodeKind::Struct || c.kind == NodeKind::Array)
|
||||
? structSpan(c.id, childMap, visited) : c.byteSize();
|
||||
int end = c.offset + sz;
|
||||
if (end > maxEnd) maxEnd = end;
|
||||
int64_t end = (int64_t)c.offset + sz;
|
||||
if (end > maxEnd) maxEnd = (int)qMin(end, (int64_t)INT_MAX);
|
||||
}
|
||||
|
||||
// Embedded struct reference: no own children but refId points to a struct definition
|
||||
@@ -373,9 +464,6 @@ struct NodeTree {
|
||||
return qMax(declaredSize, maxEnd);
|
||||
}
|
||||
|
||||
// Compute natural alignment of a struct (max alignment of direct children)
|
||||
int computeStructAlignment(uint64_t structId) const;
|
||||
|
||||
// Batch selection normalizers
|
||||
QSet<uint64_t> normalizePreferAncestors(const QSet<uint64_t>& ids) const;
|
||||
QSet<uint64_t> normalizePreferDescendants(const QSet<uint64_t>& ids) const;
|
||||
@@ -383,6 +471,10 @@ struct NodeTree {
|
||||
QJsonObject toJson() const {
|
||||
QJsonObject o;
|
||||
o["baseAddress"] = QString::number(baseAddress, 16);
|
||||
if (!baseAddressFormula.isEmpty())
|
||||
o["baseAddressFormula"] = baseAddressFormula;
|
||||
if (pointerSize != 8)
|
||||
o["pointerSize"] = pointerSize;
|
||||
o["nextId"] = QString::number(m_nextId);
|
||||
QJsonArray arr;
|
||||
for (const auto& n : nodes) arr.append(n.toJson());
|
||||
@@ -393,8 +485,11 @@ struct NodeTree {
|
||||
static NodeTree fromJson(const QJsonObject& o) {
|
||||
NodeTree t;
|
||||
t.baseAddress = o["baseAddress"].toString("400000").toULongLong(nullptr, 16);
|
||||
t.baseAddressFormula = o["baseAddressFormula"].toString();
|
||||
t.pointerSize = o["pointerSize"].toInt(8);
|
||||
t.m_nextId = o["nextId"].toString("1").toULongLong();
|
||||
QJsonArray arr = o["nodes"].toArray();
|
||||
t.nodes.reserve(arr.size());
|
||||
for (const auto& v : arr) {
|
||||
Node n = Node::fromJson(v.toObject());
|
||||
t.nodes.append(n);
|
||||
@@ -405,6 +500,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 ──
|
||||
|
||||
enum class LineKind : uint8_t {
|
||||
@@ -417,6 +572,30 @@ static constexpr uint64_t kCommandRowId = UINT64_MAX;
|
||||
static constexpr int kCommandRowLine = 0;
|
||||
static constexpr int kFirstDataLine = 1;
|
||||
static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL;
|
||||
static constexpr uint64_t kArrayElemBit = 0x4000000000000000ULL; // marks array element selection
|
||||
static constexpr uint64_t kArrayElemShift = 42; // bits 42-61 hold element index
|
||||
static constexpr uint64_t kArrayElemMask = 0x3FFFFC0000000000ULL; // 20 bits → max 1048575 elements
|
||||
|
||||
// Encode an array element selection ID: nodeId | kArrayElemBit | (elemIdx << 42)
|
||||
inline uint64_t makeArrayElemSelId(uint64_t nodeId, int elemIdx) {
|
||||
Q_ASSERT(elemIdx >= 0);
|
||||
return nodeId | kArrayElemBit | ((uint64_t)(elemIdx & 0xFFFFF) << kArrayElemShift);
|
||||
}
|
||||
inline int arrayElemIdxFromSelId(uint64_t selId) {
|
||||
return (int)((selId & kArrayElemMask) >> kArrayElemShift);
|
||||
}
|
||||
|
||||
// Member selection encoding (enum/bitfield members) — mirrors array element pattern
|
||||
static constexpr uint64_t kMemberBit = 0x2000000000000000ULL;
|
||||
static constexpr uint64_t kMemberSubShift = 42;
|
||||
static constexpr uint64_t kMemberSubMask = 0x3FFFFC0000000000ULL;
|
||||
|
||||
inline uint64_t makeMemberSelId(uint64_t nodeId, int subLine) {
|
||||
return nodeId | kMemberBit | ((uint64_t)(subLine & 0xFFFFF) << kMemberSubShift);
|
||||
}
|
||||
inline int memberSubFromSelId(uint64_t selId) {
|
||||
return (int)((selId & kMemberSubMask) >> kMemberSubShift);
|
||||
}
|
||||
|
||||
struct LineMeta {
|
||||
int nodeIdx = -1;
|
||||
@@ -437,14 +616,21 @@ struct LineMeta {
|
||||
int arrayElementIdx = -1; // Index of this element within parent array (-1 if not array element)
|
||||
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;
|
||||
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
|
||||
int lineByteCount = 0; // Hex preview: actual data byte count on this line
|
||||
int effectiveTypeW = 14; // Per-line type 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")
|
||||
bool isArrayElement = false; // true for synthesized primitive array element lines
|
||||
bool isMemberLine = false; // true for enum member / bitfield member lines
|
||||
bool isStaticLine = false; // true for static field node lines
|
||||
QString typeHint; // Type inference hint text (e.g. "Float×2") — only set for hex nodes when hints enabled
|
||||
int typeHintStart = -1; // Character offset where hint text starts in line text (-1 = none)
|
||||
QVector<NodeKind> typeHintKinds; // Suggested kinds from inference (empty = no hint)
|
||||
};
|
||||
|
||||
inline bool isSyntheticLine(const LineMeta& lm) {
|
||||
@@ -458,6 +644,7 @@ struct LayoutInfo {
|
||||
int nameW = 22; // Effective name column width (default = kColName)
|
||||
int offsetHexDigits = 8; // Hex digits for offset margin (4/8/12/16)
|
||||
uint64_t baseAddress = 0; // Base address for relative offset computation
|
||||
bool treeLines = false; // Whether tree line connectors are embedded in the text
|
||||
};
|
||||
|
||||
// ── ComposeResult ──
|
||||
@@ -479,7 +666,7 @@ namespace cmd {
|
||||
struct Insert { Node node; QVector<OffsetAdj> offAdjs; };
|
||||
struct Remove { uint64_t nodeId; QVector<Node> subtree;
|
||||
QVector<OffsetAdj> offAdjs; };
|
||||
struct ChangeBase { uint64_t oldBase, newBase; };
|
||||
struct ChangeBase { uint64_t oldBase, newBase; QString oldFormula, newFormula; };
|
||||
struct WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; };
|
||||
struct ChangeArrayMeta { uint64_t nodeId;
|
||||
NodeKind oldElementKind, newElementKind;
|
||||
@@ -489,13 +676,19 @@ namespace cmd {
|
||||
struct ChangeStructTypeName { uint64_t nodeId; QString oldName, newName; };
|
||||
struct ChangeClassKeyword { uint64_t nodeId; QString oldKeyword, newKeyword; };
|
||||
struct ChangeOffset { uint64_t nodeId; int oldOffset, newOffset; };
|
||||
struct ChangeEnumMembers { uint64_t nodeId;
|
||||
QVector<QPair<QString, int64_t>> oldMembers, newMembers; };
|
||||
struct ChangeOffsetExpr { uint64_t nodeId; QString oldExpr, newExpr; };
|
||||
struct ToggleStatic { uint64_t nodeId; bool oldVal, newVal; };
|
||||
struct ToggleRelative { uint64_t nodeId; bool oldVal, newVal; };
|
||||
}
|
||||
|
||||
using Command = std::variant<
|
||||
cmd::ChangeKind, cmd::Rename, cmd::Collapse,
|
||||
cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes,
|
||||
cmd::ChangeArrayMeta, cmd::ChangePointerRef, cmd::ChangeStructTypeName,
|
||||
cmd::ChangeClassKeyword, cmd::ChangeOffset
|
||||
cmd::ChangeClassKeyword, cmd::ChangeOffset, cmd::ChangeEnumMembers,
|
||||
cmd::ChangeOffsetExpr, cmd::ToggleStatic, cmd::ToggleRelative
|
||||
>;
|
||||
|
||||
// ── Column spans (for inline editing) ──
|
||||
@@ -508,7 +701,7 @@ struct ColumnSpan {
|
||||
|
||||
enum class EditTarget { Name, Type, Value, BaseAddress, Source, ArrayIndex, ArrayCount,
|
||||
ArrayElementType, ArrayElementCount, PointerTarget,
|
||||
RootClassType, RootClassName, TypeSelector };
|
||||
RootClassType, RootClassName, TypeSelector, StaticExpr };
|
||||
|
||||
// Column layout constants (shared with format.cpp span computation)
|
||||
inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line
|
||||
@@ -518,19 +711,20 @@ inline constexpr int kColValue = 96;
|
||||
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
||||
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
|
||||
inline constexpr int kSepWidth = 1;
|
||||
inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uint64_t")
|
||||
inline constexpr int kMinTypeW = 7; // Minimum type column width (fits "uint8_t")
|
||||
inline constexpr int kMaxTypeW = 128; // Maximum type column width
|
||||
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
||||
inline constexpr int kMaxNameW = 128; // Maximum name column width
|
||||
inline constexpr int kCompactTypeW = 20; // Type column cap for compact column mode
|
||||
|
||||
inline ColumnSpan typeSpanFor(const LineMeta& lm, int typeW = kColType) {
|
||||
if (lm.lineKind != LineKind::Field || lm.isContinuation) return {};
|
||||
if (lm.lineKind != LineKind::Field || lm.isContinuation || lm.isMemberLine) return {};
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
return {ind, ind + typeW, true};
|
||||
}
|
||||
|
||||
inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int nameW = kColName) {
|
||||
if (lm.isContinuation || lm.lineKind != LineKind::Field) return {};
|
||||
if (lm.isContinuation || lm.lineKind != LineKind::Field || lm.isMemberLine) return {};
|
||||
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
int start = ind + typeW + kSepWidth;
|
||||
@@ -545,6 +739,7 @@ inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int name
|
||||
inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW = kColType, int nameW = kColName) {
|
||||
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer ||
|
||||
lm.lineKind == LineKind::ArrayElementSeparator) return {};
|
||||
if (lm.isMemberLine) return {};
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
|
||||
// Hex uses nameW for ASCII column (same as regular name column)
|
||||
@@ -563,6 +758,45 @@ inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW
|
||||
return {start, start + valWidth, true};
|
||||
}
|
||||
|
||||
// Member line spans (enum "name = value", bitfield "name : N = value")
|
||||
inline ColumnSpan memberNameSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||
if (!lm.isMemberLine) return {};
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
int eq = lineText.indexOf(QLatin1String(" = "), ind);
|
||||
if (eq < 0) return {};
|
||||
int nameEnd = eq;
|
||||
while (nameEnd > ind && lineText[nameEnd - 1] == ' ') nameEnd--;
|
||||
return {ind, nameEnd, true};
|
||||
}
|
||||
|
||||
inline ColumnSpan memberValueSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||
if (!lm.isMemberLine) return {};
|
||||
int eq = lineText.indexOf(QLatin1String(" = "));
|
||||
if (eq < 0) return {};
|
||||
int valStart = eq + 3;
|
||||
int valEnd = lineText.size();
|
||||
while (valEnd > valStart && lineText[valEnd - 1] == ' ') valEnd--;
|
||||
return {valStart, valEnd, true};
|
||||
}
|
||||
|
||||
// Static field expression span: locates text between "return " and "→" / "(error)" / end
|
||||
inline ColumnSpan staticExprSpanFor(const LineMeta& /*lm*/, const QString& lineText) {
|
||||
int ret = lineText.indexOf(QLatin1String("return "));
|
||||
if (ret < 0) return {};
|
||||
int exprStart = ret + 7;
|
||||
// End: before arrow, before "(error)", or line end
|
||||
int exprEnd = lineText.size();
|
||||
int arrow = lineText.indexOf(QChar(0x2192), exprStart);
|
||||
if (arrow > exprStart) exprEnd = arrow;
|
||||
int err = lineText.indexOf(QLatin1String("(error)"), exprStart);
|
||||
if (err > exprStart && err < exprEnd) exprEnd = err;
|
||||
// Also stop at " }" for collapsed format
|
||||
int brace = lineText.indexOf(QLatin1String(" }"), exprStart);
|
||||
if (brace > exprStart && brace < exprEnd) exprEnd = brace;
|
||||
while (exprEnd > exprStart && lineText[exprEnd - 1] == ' ') exprEnd--;
|
||||
return {exprStart, exprEnd, true};
|
||||
}
|
||||
|
||||
inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW = kColType, int nameW = kColName) {
|
||||
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
@@ -584,50 +818,56 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
||||
// Line format: "source▾ · 0x140000000"
|
||||
|
||||
inline ColumnSpan commandRowSrcSpan(const QString& lineText) {
|
||||
int idx = lineText.indexOf(QStringLiteral(" \u00B7"));
|
||||
if (idx < 0) return {};
|
||||
// Source label ends at the ▾ dropdown arrow
|
||||
int arrow = lineText.indexOf(QChar(0x25BE));
|
||||
if (arrow < 0) return {};
|
||||
int start = 0;
|
||||
while (start < idx && !lineText[start].isLetterOrNumber()
|
||||
while (start < arrow && !lineText[start].isLetterOrNumber()
|
||||
&& lineText[start] != '<' && lineText[start] != '\'') start++;
|
||||
if (start >= idx) return {};
|
||||
// Exclude trailing ▾ from the editable span
|
||||
int end = idx;
|
||||
while (end > start && lineText[end - 1] == QChar(0x25BE)) end--;
|
||||
if (end <= start) return {};
|
||||
return {start, end, true};
|
||||
}
|
||||
|
||||
inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
|
||||
int tag = lineText.indexOf(QStringLiteral(" \u00B7"));
|
||||
if (tag < 0) return {};
|
||||
int start = tag + 3; // after " · "
|
||||
int end = start;
|
||||
while (end < lineText.size() && !lineText[end].isSpace()) end++;
|
||||
if (end <= start) return {};
|
||||
return {start, end, true};
|
||||
if (start >= arrow) return {};
|
||||
return {start, arrow, true};
|
||||
}
|
||||
|
||||
// ── CommandRow root-class spans ──
|
||||
// Combined CommandRow format ends with: " struct▾ ClassName {"
|
||||
// Combined CommandRow format ends with: " struct ClassName {"
|
||||
|
||||
inline int commandRowRootStart(const QString& lineText) {
|
||||
int best = -1;
|
||||
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;
|
||||
i = lineText.lastIndexOf(QStringLiteral("class\u25BE"));
|
||||
i = lineText.lastIndexOf(QStringLiteral("class "));
|
||||
if (i > best) best = i;
|
||||
i = lineText.lastIndexOf(QStringLiteral("enum\u25BE"));
|
||||
i = lineText.lastIndexOf(QStringLiteral("enum "));
|
||||
if (i > best) best = i;
|
||||
return best;
|
||||
}
|
||||
|
||||
inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
|
||||
// Address starts at "0x" after the source dropdown arrow
|
||||
int arrow = lineText.indexOf(QChar(0x25BE));
|
||||
if (arrow < 0) return {};
|
||||
int start = lineText.indexOf(QStringLiteral("0x"), arrow);
|
||||
if (start < 0) {
|
||||
// Formula mode: address is between arrow and root keyword
|
||||
start = arrow + 1;
|
||||
while (start < lineText.size() && lineText[start].isSpace()) start++;
|
||||
}
|
||||
// End at root keyword (struct/class/enum) or end of line
|
||||
int rootStart = commandRowRootStart(lineText);
|
||||
int end = (rootStart > start) ? rootStart : lineText.size();
|
||||
// Trim trailing whitespace
|
||||
while (end > start && lineText[end - 1].isSpace()) end--;
|
||||
if (end <= start) return {};
|
||||
return {start, end, true};
|
||||
}
|
||||
|
||||
inline ColumnSpan commandRowRootTypeSpan(const QString& lineText) {
|
||||
int start = commandRowRootStart(lineText);
|
||||
if (start < 0) return {};
|
||||
int end = start;
|
||||
while (end < lineText.size() && lineText[end] != QChar(' ')
|
||||
&& lineText[end] != QChar(0x25BE)) end++;
|
||||
while (end < lineText.size() && lineText[end] != QChar(' ')) end++;
|
||||
if (end <= start) return {};
|
||||
return {start, end, true};
|
||||
}
|
||||
@@ -771,17 +1011,18 @@ namespace fmt {
|
||||
QString fmtNodeLine(const Node& node, const Provider& prov,
|
||||
uint64_t addr, int depth, int subLine = 0,
|
||||
const QString& comment = {}, int colType = kColType, int colName = kColName,
|
||||
const QString& typeOverride = {});
|
||||
const QString& typeOverride = {}, bool compact = false);
|
||||
QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDigits = 8);
|
||||
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType = kColType, int colName = kColName);
|
||||
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType = kColType, int colName = kColName, bool compact = false);
|
||||
QString fmtStructFooter(const Node& node, int depth, int totalSize = -1);
|
||||
QString fmtArrayHeader(const Node& node, int depth, int viewIdx, bool collapsed, int colType = kColType, int colName = kColName, const QString& elemStructName = {});
|
||||
QString fmtArrayHeader(const Node& node, int depth, int viewIdx, bool collapsed, int colType = kColType, int colName = kColName, const QString& elemStructName = {}, bool compact = false);
|
||||
QString structTypeName(const Node& node); // Full type string for struct headers
|
||||
QString arrayTypeName(NodeKind elemKind, int count, const QString& structName = {});
|
||||
QString pointerTypeName(NodeKind kind, const QString& targetName);
|
||||
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
||||
const Provider& prov, uint64_t addr,
|
||||
const QString& ptrTypeName, int colType = kColType, int colName = kColName);
|
||||
const QString& ptrTypeName, int colType = kColType, int colName = kColName,
|
||||
bool compact = false);
|
||||
QString validateBaseAddress(const QString& text);
|
||||
QString indent(int depth);
|
||||
QString readValue(const Node& node, const Provider& prov,
|
||||
@@ -791,10 +1032,23 @@ namespace fmt {
|
||||
QByteArray parseValue(NodeKind kind, const QString& text, bool* ok);
|
||||
QByteArray parseAsciiValue(const QString& text, int expectedSize, bool* ok);
|
||||
QString validateValue(NodeKind kind, const QString& text);
|
||||
QString fmtEnumMember(const QString& name, int64_t value, int depth, int nameW);
|
||||
QString fmtBitfieldMember(const QString& name, uint8_t bitWidth,
|
||||
uint64_t value, int depth, int nameW);
|
||||
uint64_t extractBits(const Provider& prov, uint64_t addr,
|
||||
NodeKind containerKind,
|
||||
uint8_t bitOffset, uint8_t bitWidth);
|
||||
} // namespace fmt
|
||||
|
||||
// ── Compose function forward declaration ──
|
||||
|
||||
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0);
|
||||
// Optional callback: given an absolute address, return a symbol name (e.g. "nt!PsActiveProcessHead")
|
||||
// or empty string if no symbol matches. Used for PDB symbol annotations on rows.
|
||||
using SymbolLookupFn = std::function<QString(uint64_t addr)>;
|
||||
|
||||
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0,
|
||||
bool compactColumns = false, bool treeLines = false,
|
||||
bool braceWrap = false, bool typeHints = false,
|
||||
SymbolLookupFn symbolLookup = {});
|
||||
|
||||
} // namespace rcx
|
||||
|
||||
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
|
||||
36
src/dock_tab_buttons.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QToolButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
|
||||
// Dock tab button widget (close button)
|
||||
// Placed on the right side of each dock tab via QTabBar::setTabButton.
|
||||
class DockTabButtons : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QToolButton* closeBtn;
|
||||
|
||||
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
|
||||
auto* hl = new QHBoxLayout(this);
|
||||
hl->setContentsMargins(0, 0, 0, 0);
|
||||
hl->setSpacing(0);
|
||||
|
||||
closeBtn = new QToolButton(this);
|
||||
closeBtn->setAutoRaise(true);
|
||||
closeBtn->setCursor(Qt::PointingHandCursor);
|
||||
closeBtn->setFixedSize(16, 16);
|
||||
closeBtn->setToolTip("Close tab");
|
||||
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
|
||||
closeBtn->setIconSize(QSize(12, 12));
|
||||
hl->addWidget(closeBtn);
|
||||
}
|
||||
|
||||
void applyTheme(const QColor& hover) {
|
||||
QString style = QStringLiteral(
|
||||
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
||||
"QToolButton:hover { background: %1; }").arg(hover.name());
|
||||
closeBtn->setStyleSheet(style);
|
||||
}
|
||||
};
|
||||
2083
src/editor.cpp
67
src/editor.h
@@ -1,20 +1,18 @@
|
||||
#pragma once
|
||||
#include "core.h"
|
||||
#include "providerregistry.h"
|
||||
#include "themes/theme.h"
|
||||
#include <QWidget>
|
||||
#include <QSet>
|
||||
#include <QPoint>
|
||||
#include <QHash>
|
||||
|
||||
class QLineEdit;
|
||||
class QsciScintilla;
|
||||
class QsciLexerCPP;
|
||||
|
||||
namespace rcx {
|
||||
|
||||
struct SavedSourceDisplay {
|
||||
QString text;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
class RcxEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -27,9 +25,15 @@ public:
|
||||
void restoreViewState(const ViewState& vs);
|
||||
|
||||
QsciScintilla* scintilla() const { return m_sci; }
|
||||
QWidget* historyPopup() const { return m_historyPopup; }
|
||||
QWidget* disasmPopup() const { return m_disasmPopup; }
|
||||
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
|
||||
const LineMeta* metaForLine(int line) const;
|
||||
int currentNodeIndex() const;
|
||||
void scrollToNodeId(uint64_t nodeId);
|
||||
void showFindBar();
|
||||
void dismissHistoryPopup();
|
||||
void dismissAllPopups();
|
||||
|
||||
// ── Column span computation ──
|
||||
static ColumnSpan typeSpan(const LineMeta& lm, int typeW = kColType);
|
||||
@@ -43,6 +47,8 @@ public:
|
||||
bool isEditing() const { return m_editState.active; }
|
||||
bool beginInlineEdit(EditTarget target, int line = -1, int col = -1);
|
||||
void cancelInlineEdit();
|
||||
void setHexEditPending(bool v) { m_hexEditPending = v; }
|
||||
void setStaticCompletions(const QStringList& words) { m_staticCompletions = words; }
|
||||
|
||||
void applySelectionOverlay(const QSet<uint64_t>& selIds);
|
||||
void setCommandRowText(const QString& line);
|
||||
@@ -54,6 +60,12 @@ public:
|
||||
// Custom type names (struct types from the tree) shown in type picker + lexer GlobalClass coloring
|
||||
QString textWithMargins() const;
|
||||
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
|
||||
void setSavedSources(const QVector<SavedSourceDisplay>& sources) { m_savedSourceDisplay = sources; }
|
||||
@@ -61,12 +73,19 @@ public:
|
||||
signals:
|
||||
void marginClicked(int margin, int line, Qt::KeyboardModifiers mods);
|
||||
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 inlineEditCommitted(int nodeIdx, int subLine,
|
||||
EditTarget target, const QString& text);
|
||||
EditTarget target, const QString& text,
|
||||
uint64_t resolvedAddr = 0);
|
||||
void inlineEditCancelled();
|
||||
void typeSelectorRequested();
|
||||
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
|
||||
void insertAboveRequested(int nodeIdx, NodeKind kind);
|
||||
void relativeOffsetsChanged(bool relative);
|
||||
void appendBytesRequested(uint64_t structId, int byteCount);
|
||||
void trimHexRequested(uint64_t structId);
|
||||
void appendEnumMembersRequested(uint64_t enumId, int count);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
@@ -78,7 +97,7 @@ private:
|
||||
LayoutInfo m_layout; // cached from ComposeResult
|
||||
|
||||
// ── Toggle: absolute vs relative offset margin
|
||||
bool m_relativeOffsets = false;
|
||||
bool m_relativeOffsets = true;
|
||||
|
||||
int m_marginStyleBase = -1;
|
||||
int m_hintLine = -1;
|
||||
@@ -88,8 +107,12 @@ private:
|
||||
bool m_hoverInside = false;
|
||||
uint64_t m_hoveredNodeId = 0;
|
||||
int m_hoveredLine = -1;
|
||||
uint64_t m_prevHoveredNodeId = 0; // for incremental marker update
|
||||
int m_prevHoveredLine = -1; // for incremental marker update
|
||||
QSet<uint64_t> m_currentSelIds;
|
||||
QVector<int> m_hoverSpanLines; // Lines with hover span indicators
|
||||
// ── nodeId → display-line index (built in applyDocument) ──
|
||||
QHash<uint64_t, QVector<int>> m_nodeLineIndex;
|
||||
// ── Drag selection ──
|
||||
bool m_dragging = false;
|
||||
bool m_dragStarted = false; // true once drag threshold exceeded
|
||||
@@ -117,8 +140,10 @@ private:
|
||||
NodeKind editKind = NodeKind::Int32;
|
||||
int commentCol = -1; // fixed comment column (stored at edit start)
|
||||
bool lastValidationOk = true; // track state to avoid redundant updates
|
||||
bool hexOverwrite = false; // true for hex-byte / ASCII-preview fixed-length editing
|
||||
};
|
||||
InlineEditState m_editState;
|
||||
QStringList m_staticCompletions; // autocomplete words for StaticExpr editing
|
||||
|
||||
// ── Tab cycling state ──
|
||||
EditTarget m_lastTabTarget = EditTarget::Value;
|
||||
@@ -129,7 +154,27 @@ private:
|
||||
// ── Saved sources for quick-switch ──
|
||||
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; // TitleBodyPopup (file-local class in editor.cpp)
|
||||
QWidget* m_structPreviewPopup = nullptr; // TitleBodyPopup (file-local class in editor.cpp)
|
||||
QWidget* m_arrowTooltip = nullptr; // RcxTooltip (arrow callout)
|
||||
const Provider* m_disasmProvider = nullptr; // snapshot or real — for reading tree data
|
||||
const Provider* m_disasmRealProv = nullptr; // real process provider — for reading code at arbitrary addresses
|
||||
const NodeTree* m_disasmTree = nullptr;
|
||||
|
||||
// ── Find bar ──
|
||||
QWidget* m_findBarContainer = nullptr;
|
||||
QLineEdit* m_findBar = nullptr;
|
||||
long m_findPos = 0;
|
||||
void hideFindBar();
|
||||
|
||||
// ── Hex inline edit ──
|
||||
bool m_hexEditPending = false; // set by context menu before calling beginInlineEdit
|
||||
|
||||
// ── Reentrancy guards ──
|
||||
bool m_applyingDocument = false;
|
||||
bool m_clampingSelection = false;
|
||||
bool m_updatingComment = false;
|
||||
|
||||
@@ -140,12 +185,11 @@ private:
|
||||
void setupMarkers();
|
||||
void allocateMarginStyles();
|
||||
|
||||
void applyMarginText(const QVector<LineMeta>& meta);
|
||||
void applyLineAttributes(const QVector<LineMeta>& meta);
|
||||
void reformatMargins();
|
||||
void applyMarkers(const QVector<LineMeta>& meta);
|
||||
void applyFoldLevels(const QVector<LineMeta>& meta);
|
||||
void applyHexDimming(const QVector<LineMeta>& meta);
|
||||
void applyDataChangedHighlight(const QVector<LineMeta>& meta);
|
||||
void applyHeatmapHighlight(const QVector<LineMeta>& meta, const QVector<QString>& lineTexts);
|
||||
void applySymbolColoring(const QVector<LineMeta>& meta, const QVector<QString>& lineTexts);
|
||||
void applyBaseAddressColoring(const QVector<LineMeta>& meta);
|
||||
void applyCommandRowPills();
|
||||
|
||||
@@ -153,6 +197,7 @@ private:
|
||||
int editEndCol() const;
|
||||
bool handleNormalKey(QKeyEvent* ke);
|
||||
bool handleEditKey(QKeyEvent* ke);
|
||||
bool handleHexEditKey(QKeyEvent* ke);
|
||||
void showTypeAutocomplete();
|
||||
void showSourcePicker();
|
||||
void showTypeListFiltered(const QString& filter);
|
||||
|
||||
1
src/examples/EPROCESS.rcx
Normal file
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
|
||||
}
|
||||
]
|
||||
}
|
||||
126
src/examples/PageTables.rcx
Normal file
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"baseAddress": "0",
|
||||
"nextId": "2000",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100",
|
||||
"kind": "Struct",
|
||||
"name": "pte",
|
||||
"structTypeName": "X64_PTE",
|
||||
"classKeyword": "bitfield",
|
||||
"elementKind": "UInt64",
|
||||
"offset": 0,
|
||||
"parentId": "0",
|
||||
"refId": "0",
|
||||
"collapsed": true,
|
||||
"arrayLen": 1,
|
||||
"strLen": 64,
|
||||
"bitfieldMembers": [
|
||||
{"name": "Present", "bitOffset": 0, "bitWidth": 1},
|
||||
{"name": "ReadWrite", "bitOffset": 1, "bitWidth": 1},
|
||||
{"name": "UserSuper", "bitOffset": 2, "bitWidth": 1},
|
||||
{"name": "WriteThrough", "bitOffset": 3, "bitWidth": 1},
|
||||
{"name": "CacheDisable", "bitOffset": 4, "bitWidth": 1},
|
||||
{"name": "Accessed", "bitOffset": 5, "bitWidth": 1},
|
||||
{"name": "Dirty", "bitOffset": 6, "bitWidth": 1},
|
||||
{"name": "PageSize", "bitOffset": 7, "bitWidth": 1},
|
||||
{"name": "Global", "bitOffset": 8, "bitWidth": 1},
|
||||
{"name": "AVL", "bitOffset": 9, "bitWidth": 3},
|
||||
{"name": "PhysAddr", "bitOffset": 12, "bitWidth": 40},
|
||||
{"name": "Available", "bitOffset": 52, "bitWidth": 7},
|
||||
{"name": "ProtKey", "bitOffset": 59, "bitWidth": 4},
|
||||
{"name": "NX", "bitOffset": 63, "bitWidth": 1}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"id": "200",
|
||||
"kind": "Struct",
|
||||
"name": "page_table",
|
||||
"structTypeName": "X64_PAGE_TABLE",
|
||||
"offset": 0,
|
||||
"parentId": "0",
|
||||
"refId": "0",
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"id": "201",
|
||||
"kind": "Array",
|
||||
"name": "entries",
|
||||
"offset": 0,
|
||||
"parentId": "200",
|
||||
"refId": "100",
|
||||
"elementKind": "Struct",
|
||||
"arrayLen": 512,
|
||||
"strLen": 64,
|
||||
"collapsed": true
|
||||
},
|
||||
|
||||
{
|
||||
"id": "300",
|
||||
"kind": "Struct",
|
||||
"name": "pde_2mb",
|
||||
"structTypeName": "X64_PDE_LARGE",
|
||||
"classKeyword": "bitfield",
|
||||
"elementKind": "UInt64",
|
||||
"offset": 0,
|
||||
"parentId": "0",
|
||||
"refId": "0",
|
||||
"collapsed": true,
|
||||
"arrayLen": 1,
|
||||
"strLen": 64,
|
||||
"bitfieldMembers": [
|
||||
{"name": "Present", "bitOffset": 0, "bitWidth": 1},
|
||||
{"name": "ReadWrite", "bitOffset": 1, "bitWidth": 1},
|
||||
{"name": "UserSuper", "bitOffset": 2, "bitWidth": 1},
|
||||
{"name": "WriteThrough", "bitOffset": 3, "bitWidth": 1},
|
||||
{"name": "CacheDisable", "bitOffset": 4, "bitWidth": 1},
|
||||
{"name": "Accessed", "bitOffset": 5, "bitWidth": 1},
|
||||
{"name": "Dirty", "bitOffset": 6, "bitWidth": 1},
|
||||
{"name": "PageSize", "bitOffset": 7, "bitWidth": 1},
|
||||
{"name": "Global", "bitOffset": 8, "bitWidth": 1},
|
||||
{"name": "AVL", "bitOffset": 9, "bitWidth": 3},
|
||||
{"name": "PAT", "bitOffset": 12, "bitWidth": 1},
|
||||
{"name": "Reserved", "bitOffset": 13, "bitWidth": 8},
|
||||
{"name": "PhysAddr", "bitOffset": 21, "bitWidth": 31},
|
||||
{"name": "Available", "bitOffset": 52, "bitWidth": 7},
|
||||
{"name": "ProtKey", "bitOffset": 59, "bitWidth": 4},
|
||||
{"name": "NX", "bitOffset": 63, "bitWidth": 1}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"id": "400",
|
||||
"kind": "Struct",
|
||||
"name": "pdpte_1gb",
|
||||
"structTypeName": "X64_PDPTE_HUGE",
|
||||
"classKeyword": "bitfield",
|
||||
"elementKind": "UInt64",
|
||||
"offset": 0,
|
||||
"parentId": "0",
|
||||
"refId": "0",
|
||||
"collapsed": true,
|
||||
"arrayLen": 1,
|
||||
"strLen": 64,
|
||||
"bitfieldMembers": [
|
||||
{"name": "Present", "bitOffset": 0, "bitWidth": 1},
|
||||
{"name": "ReadWrite", "bitOffset": 1, "bitWidth": 1},
|
||||
{"name": "UserSuper", "bitOffset": 2, "bitWidth": 1},
|
||||
{"name": "WriteThrough", "bitOffset": 3, "bitWidth": 1},
|
||||
{"name": "CacheDisable", "bitOffset": 4, "bitWidth": 1},
|
||||
{"name": "Accessed", "bitOffset": 5, "bitWidth": 1},
|
||||
{"name": "Dirty", "bitOffset": 6, "bitWidth": 1},
|
||||
{"name": "PageSize", "bitOffset": 7, "bitWidth": 1},
|
||||
{"name": "Global", "bitOffset": 8, "bitWidth": 1},
|
||||
{"name": "AVL", "bitOffset": 9, "bitWidth": 3},
|
||||
{"name": "PAT", "bitOffset": 12, "bitWidth": 1},
|
||||
{"name": "Reserved", "bitOffset": 13, "bitWidth": 17},
|
||||
{"name": "PhysAddr", "bitOffset": 30, "bitWidth": 22},
|
||||
{"name": "Available", "bitOffset": 52, "bitWidth": 7},
|
||||
{"name": "ProtKey", "bitOffset": 59, "bitWidth": 4},
|
||||
{"name": "NX", "bitOffset": 63, "bitWidth": 1}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rootIds": ["200"]
|
||||
}
|
||||
1
src/examples/Vergilius_25H2.rcx
Normal file
1
src/examples/WinSDK.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": 4,
|
||||
"collapsed": false,
|
||||
"elementKind": "Float",
|
||||
"id": "27",
|
||||
"kind": "Array",
|
||||
"name": "scores",
|
||||
"offset": 112,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 2,
|
||||
"collapsed": false,
|
||||
"elementKind": "Struct",
|
||||
"id": "28",
|
||||
"kind": "Array",
|
||||
"name": "materials",
|
||||
"offset": 128,
|
||||
"parentId": "1",
|
||||
"refId": "20",
|
||||
"strLen": 64
|
||||
}
|
||||
]
|
||||
}
|
||||
10755
src/examples/t6zm.rcx
Normal file
42817
src/examples/windows-x86_64.h
Normal file
253
src/format.cpp
@@ -1,4 +1,5 @@
|
||||
#include "core.h"
|
||||
#include "addressparser.h"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
@@ -22,6 +23,14 @@ static QString fit(QString s, int w) {
|
||||
return s.leftJustified(w, ' ');
|
||||
}
|
||||
|
||||
// Like fit() but overflows instead of truncating: if s exceeds w, return full string
|
||||
static QString fitOverflow(const QString& s, int w) {
|
||||
if (w <= 0) return {};
|
||||
if (s.size() <= w)
|
||||
return s.leftJustified(w, ' ');
|
||||
return s;
|
||||
}
|
||||
|
||||
// ── Type name ──
|
||||
|
||||
// Override seam: injectable type-name provider
|
||||
@@ -64,45 +73,45 @@ QString pointerTypeName(NodeKind kind, const QString& targetName) {
|
||||
// ── Value formatting ──
|
||||
|
||||
static QString hexVal(uint64_t v) {
|
||||
return QStringLiteral("0x") + QString::number(v, 16);
|
||||
return QString::asprintf("0x%llx", (unsigned long long)v);
|
||||
}
|
||||
|
||||
static QString rawHex(uint64_t v, int digits) {
|
||||
return QString::number(v, 16).rightJustified(digits, '0');
|
||||
}
|
||||
|
||||
QString fmtInt8(int8_t v) { return hexVal((uint8_t)v); }
|
||||
QString fmtInt16(int16_t v) { return hexVal((uint16_t)v); }
|
||||
QString fmtInt32(int32_t v) { return hexVal((uint32_t)v); }
|
||||
QString fmtInt64(int64_t v) { return hexVal((uint64_t)v); }
|
||||
QString fmtInt8(int8_t v) { return QString::number(v); }
|
||||
QString fmtInt16(int16_t v) { return QString::number(v); }
|
||||
QString fmtInt32(int32_t v) { return QString::number(v); }
|
||||
QString fmtInt64(int64_t v) { return QString::number((qlonglong)v); }
|
||||
QString fmtUInt8(uint8_t v) { return hexVal(v); }
|
||||
QString fmtUInt16(uint16_t v) { return hexVal(v); }
|
||||
QString fmtUInt32(uint32_t v) { return hexVal(v); }
|
||||
QString fmtUInt64(uint64_t v) { return hexVal(v); }
|
||||
|
||||
QString fmtFloat(float v) {
|
||||
// Fixed 7-char body: digits + "." + decimals + "f"
|
||||
// Negative values get a '-' prefix (8 chars total), positive stay 7.
|
||||
if (std::isnan(v)) return QStringLiteral("NaN");
|
||||
if (std::isinf(v)) return v > 0 ? QStringLiteral("inff") : QStringLiteral("-inff");
|
||||
|
||||
// 6 significant digits — covers full single-precision range
|
||||
QString s = QString::number(v, 'g', 6);
|
||||
float av = std::fabs(v);
|
||||
if (av >= 100000.f)
|
||||
return v < 0 ? QStringLiteral("-99999+f") : QStringLiteral("99999+f");
|
||||
|
||||
// If 'g' chose scientific notation, reformat as plain decimal
|
||||
if (s.contains('e') || s.contains('E')) {
|
||||
s = QString::number(v, 'f', 8);
|
||||
if (s.contains('.')) {
|
||||
int i = s.size() - 1;
|
||||
while (i > 0 && s[i] == '0') i--;
|
||||
if (s[i] == '.') i++; // keep at least one decimal digit
|
||||
s.truncate(i + 1);
|
||||
// body = digits + "." + decimals + "f", target exactly 7 chars.
|
||||
// Start with max decimals, reduce if integer part is wide or rounding overflows.
|
||||
for (int dec = 4; dec >= 0; dec--) {
|
||||
QString body = QString::number(av, 'f', dec);
|
||||
body += (dec == 0) ? QStringLiteral(".f") : QStringLiteral("f");
|
||||
if (body.size() == 7) {
|
||||
if (v < 0.f) body.prepend('-');
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.contains('.'))
|
||||
s += QStringLiteral(".f");
|
||||
else
|
||||
s += QLatin1Char('f');
|
||||
return s;
|
||||
// Rounding pushed past 99999 — use overflow cap
|
||||
return v < 0 ? QStringLiteral("-99999+f") : QStringLiteral("99999+f");
|
||||
}
|
||||
QString fmtDouble(double v) {
|
||||
QString s = QString::number(v, 'g', 6);
|
||||
@@ -112,15 +121,8 @@ QString fmtDouble(double v) {
|
||||
}
|
||||
QString fmtBool(uint8_t v) { return v ? QStringLiteral("true") : QStringLiteral("false"); }
|
||||
|
||||
QString fmtPointer32(uint32_t v) {
|
||||
if (v == 0) return QStringLiteral("-> NULL");
|
||||
return QStringLiteral("-> ") + hexVal(v);
|
||||
}
|
||||
|
||||
QString fmtPointer64(uint64_t v) {
|
||||
if (v == 0) return QStringLiteral("-> NULL");
|
||||
return QStringLiteral("-> ") + hexVal(v);
|
||||
}
|
||||
QString fmtPointer32(uint32_t v) { return hexVal(v); }
|
||||
QString fmtPointer64(uint64_t v) { return hexVal(v); }
|
||||
|
||||
// ── Indentation ──
|
||||
|
||||
@@ -139,32 +141,43 @@ QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDig
|
||||
// ── Struct type name (for width calculation) ──
|
||||
|
||||
QString structTypeName(const Node& node) {
|
||||
// Full type string: "struct TypeName" or just "struct" if no typename
|
||||
QString base = typeName(node.kind).trimmed(); // "struct"
|
||||
// Named types: just the type name (e.g. "_LIST_ENTRY")
|
||||
// Anonymous: just the keyword (e.g. "union", "struct")
|
||||
if (!node.structTypeName.isEmpty())
|
||||
return base + QStringLiteral(" ") + node.structTypeName;
|
||||
return base;
|
||||
return node.structTypeName;
|
||||
return node.resolvedClassKeyword();
|
||||
}
|
||||
|
||||
// ── Struct header / footer ──
|
||||
|
||||
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType, int colName) {
|
||||
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType, int colName, bool compact) {
|
||||
// Columnar format: <type> <name> { (or no brace when collapsed)
|
||||
QString ind = indent(depth);
|
||||
QString type = fit(structTypeName(node), colType);
|
||||
QString rawType = structTypeName(node);
|
||||
QString suffix = collapsed ? QString() : QStringLiteral("{");
|
||||
if (node.name.isEmpty()) {
|
||||
// Anonymous struct/union: "union {" with no column padding
|
||||
return ind + rawType + SEP + suffix;
|
||||
}
|
||||
QString type = compact ? fitOverflow(rawType, colType) : fit(rawType, colType);
|
||||
return ind + type + SEP + node.name + SEP + suffix;
|
||||
}
|
||||
|
||||
QString fmtStructFooter(const Node& /*node*/, int depth, int /*totalSize*/) {
|
||||
return indent(depth) + QStringLiteral("};");
|
||||
QString fmtStructFooter(const Node& node, int depth, int /*totalSize*/) {
|
||||
QString footer = indent(depth) + QStringLiteral("};");
|
||||
if (node.resolvedClassKeyword() == QStringLiteral("enum"))
|
||||
footer += QStringLiteral(" +10");
|
||||
else
|
||||
footer += QStringLiteral(" +10h +100h +1000h Trim");
|
||||
return footer;
|
||||
}
|
||||
|
||||
// ── Array header ──
|
||||
// Columnar format: <type[count]> <name> { (or no brace when collapsed)
|
||||
QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collapsed, int colType, int colName, const QString& elemStructName) {
|
||||
QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collapsed, int colType, int colName, const QString& elemStructName, bool compact) {
|
||||
QString ind = indent(depth);
|
||||
QString type = fit(arrayTypeName(node.elementKind, node.arrayLen, elemStructName), colType);
|
||||
QString rawType = arrayTypeName(node.elementKind, node.arrayLen, elemStructName);
|
||||
QString type = compact ? fitOverflow(rawType, colType) : fit(rawType, colType);
|
||||
QString suffix = collapsed ? QString() : QStringLiteral("{");
|
||||
return ind + type + SEP + node.name + SEP + suffix;
|
||||
}
|
||||
@@ -173,10 +186,16 @@ QString fmtArrayHeader(const Node& node, int depth, int /*viewIdx*/, bool collap
|
||||
|
||||
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
||||
const Provider& prov, uint64_t addr,
|
||||
const QString& ptrTypeName, int colType, int colName) {
|
||||
const QString& ptrTypeName, int colType, int colName,
|
||||
bool compact) {
|
||||
QString ind = indent(depth);
|
||||
QString type = fit(ptrTypeName, colType);
|
||||
bool overflow = compact && ptrTypeName.size() > colType;
|
||||
QString type = compact ? fitOverflow(ptrTypeName, colType) : fit(ptrTypeName, colType);
|
||||
if (collapsed) {
|
||||
if (overflow) {
|
||||
// Overflow: no column padding
|
||||
return ind + type + SEP + node.name + SEP + readValue(node, prov, addr, 0);
|
||||
}
|
||||
// Collapsed: show pointer value instead of brace (name padded for value alignment)
|
||||
QString name = fit(node.name, colName);
|
||||
QString val = fit(readValue(node, prov, addr, 0), COL_VALUE);
|
||||
@@ -214,15 +233,18 @@ static QString bytesToAscii(const QByteArray& b, int slot) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static const char kHexDigits[] = "0123456789ABCDEF";
|
||||
|
||||
static QString bytesToHex(const QByteArray& b, int slot) {
|
||||
QString out;
|
||||
out.reserve(slot * 3);
|
||||
QChar buf[64]; // max slot=8 → 8*3-1=23 chars; 64 is plenty
|
||||
int pos = 0;
|
||||
for (int i = 0; i < slot; ++i) {
|
||||
uint8_t c = (i < b.size()) ? (uint8_t)b[i] : 0;
|
||||
out += QString::asprintf("%02X", (unsigned)c);
|
||||
if (i + 1 < slot) out += ' ';
|
||||
buf[pos++] = QLatin1Char(kHexDigits[c >> 4]);
|
||||
buf[pos++] = QLatin1Char(kHexDigits[c & 0xF]);
|
||||
if (i + 1 < slot) buf[pos++] = QLatin1Char(' ');
|
||||
}
|
||||
return out;
|
||||
return QString(buf, pos);
|
||||
}
|
||||
|
||||
static QString fmtAsciiAndBytes(const Provider& prov, uint64_t addr,
|
||||
@@ -262,15 +284,55 @@ static QString readValueImpl(const Node& node, const Provider& prov,
|
||||
if (!display) return rawHex(val, 8);
|
||||
QString s = fmtPointer32(val);
|
||||
QString sym = prov.getSymbol((uint64_t)val);
|
||||
if (!sym.isEmpty()) s += QStringLiteral(" ") + sym;
|
||||
if (!sym.isEmpty()) s += QStringLiteral(" // ") + sym;
|
||||
return s;
|
||||
}
|
||||
case NodeKind::Pointer64: {
|
||||
uint64_t val = prov.readU64(addr);
|
||||
// Primitive pointer: dereference and show target value
|
||||
// (hex/ptr/fnptr targets fall through to plain void* display)
|
||||
if (node.ptrDepth > 0 && isValidPrimitivePtrTarget(node.elementKind) && val != 0) {
|
||||
uint64_t target = val;
|
||||
for (int d = 1; d < node.ptrDepth && target != 0; d++)
|
||||
target = prov.isReadable(target, 8) ? prov.readU64(target) : 0;
|
||||
if (target != 0 && prov.isReadable(target, sizeForKind(node.elementKind))) {
|
||||
// Create a temporary node of the target kind to format the value
|
||||
Node tmp;
|
||||
tmp.kind = node.elementKind;
|
||||
tmp.strLen = node.strLen;
|
||||
QString derefVal = readValueImpl(tmp, prov, target, 0, mode);
|
||||
if (display) {
|
||||
QString arrow = QStringLiteral("-> ");
|
||||
QString sym = prov.getSymbol(val);
|
||||
if (!sym.isEmpty())
|
||||
return arrow + derefVal + QStringLiteral(" // ") + sym;
|
||||
return arrow + derefVal;
|
||||
}
|
||||
return derefVal;
|
||||
}
|
||||
if (!display) return rawHex(val, 16);
|
||||
return fmtPointer64(val);
|
||||
}
|
||||
if (!display) return rawHex(val, 16);
|
||||
QString s = fmtPointer64(val);
|
||||
QString sym = prov.getSymbol(val);
|
||||
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;
|
||||
}
|
||||
case NodeKind::Vec2:
|
||||
@@ -279,7 +341,7 @@ static QString readValueImpl(const Node& node, const Provider& prov,
|
||||
int count = sizeForKind(node.kind) / 4;
|
||||
QStringList parts;
|
||||
for (int i = 0; i < count; i++)
|
||||
parts << fmtFloat(prov.readF32(addr + i * 4)).trimmed();
|
||||
parts << fmtFloat(prov.readF32(addr + i * 4));
|
||||
return parts.join(QStringLiteral(", "));
|
||||
}
|
||||
case NodeKind::Mat4x4: {
|
||||
@@ -288,7 +350,7 @@ static QString readValueImpl(const Node& node, const Provider& prov,
|
||||
QString line = QStringLiteral("row%1 [").arg(subLine);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
if (c > 0) line += QStringLiteral(", ");
|
||||
line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4)).trimmed();
|
||||
line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4));
|
||||
}
|
||||
line += QStringLiteral("]");
|
||||
return line;
|
||||
@@ -325,12 +387,22 @@ QString readValue(const Node& node, const Provider& prov,
|
||||
QString fmtNodeLine(const Node& node, const Provider& prov,
|
||||
uint64_t addr, int depth, int subLine,
|
||||
const QString& comment, int colType, int colName,
|
||||
const QString& typeOverride) {
|
||||
const QString& typeOverride, bool compact) {
|
||||
QString ind = indent(depth);
|
||||
QString type = typeOverride.isEmpty() ? typeName(node.kind, colType) : fit(typeOverride, colType);
|
||||
QString name = fit(node.name, colName);
|
||||
|
||||
// Compute raw type string for overflow detection
|
||||
QString rawType = typeOverride.isEmpty() ? typeNameRaw(node.kind) : typeOverride;
|
||||
bool overflow = compact && rawType.size() > colType;
|
||||
|
||||
QString type = overflow ? fitOverflow(rawType, colType)
|
||||
: (typeOverride.isEmpty() ? typeName(node.kind, colType)
|
||||
: fit(typeOverride, colType));
|
||||
QString name = overflow ? node.name : fit(node.name, colName);
|
||||
|
||||
// Effective column width for this line (accounts for overflow, clamped to hard max)
|
||||
int effectiveColType = overflow ? rawType.size() : colType;
|
||||
// Blank prefix for continuation lines (same width as type+sep+name+sep)
|
||||
const int prefixW = colType + colName + 2 * kSepWidth;
|
||||
const int prefixW = effectiveColType + (overflow ? name.size() : colName) + 2 * kSepWidth;
|
||||
|
||||
// Comment suffix (only present when a comment is provided; no trailing padding)
|
||||
QString cmtSuffix = comment.isEmpty() ? QString()
|
||||
@@ -353,7 +425,8 @@ QString fmtNodeLine(const Node& node, const Provider& prov,
|
||||
return ind + type + SEP + ascii + SEP + hex + cmtSuffix;
|
||||
}
|
||||
|
||||
QString val = fit(readValue(node, prov, addr, subLine), COL_VALUE);
|
||||
QString val = overflow ? readValue(node, prov, addr, subLine)
|
||||
: fit(readValue(node, prov, addr, subLine), COL_VALUE);
|
||||
return ind + type + SEP + name + SEP + val + cmtSuffix;
|
||||
}
|
||||
|
||||
@@ -543,6 +616,14 @@ QByteArray parseValue(NodeKind kind, const QString& text, bool* ok) {
|
||||
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
||||
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: {
|
||||
*ok = true;
|
||||
if (s.startsWith('"') && s.endsWith('"'))
|
||||
@@ -571,7 +652,8 @@ QString validateValue(NodeKind kind, const QString& text) {
|
||||
|
||||
// For integer/hex types, validate character set first
|
||||
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);
|
||||
|
||||
if (isHexKind || isIntKind) {
|
||||
@@ -579,8 +661,10 @@ QString validateValue(NodeKind kind, const QString& text) {
|
||||
QString digits = hasHexPrefix ? s.mid(2) : s;
|
||||
|
||||
if (hasHexPrefix || isHexKind) {
|
||||
// Hex mode: only 0-9, a-f, A-F
|
||||
// Hex mode: only 0-9, a-f, A-F (spaces allowed for multi-byte hex kinds)
|
||||
bool isMultiByteHex = (kind >= NodeKind::Hex16 && kind <= NodeKind::Hex64);
|
||||
for (QChar c : digits) {
|
||||
if (c == ' ' && isMultiByteHex) continue;
|
||||
if (!c.isDigit() && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
|
||||
return QStringLiteral("invalid hex '%1'").arg(c);
|
||||
}
|
||||
@@ -615,43 +699,42 @@ QString validateValue(NodeKind kind, const QString& text) {
|
||||
return QStringLiteral("invalid");
|
||||
}
|
||||
|
||||
// ── Base address validation (supports simple +/- equations) ──
|
||||
// ── Base address validation (delegates to AddressParser) ──
|
||||
|
||||
QString validateBaseAddress(const QString& text) {
|
||||
QString s = text.trimmed();
|
||||
if (s.isEmpty()) return QStringLiteral("empty");
|
||||
//s.remove('`');
|
||||
return AddressParser::validate(s);
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
bool firstTerm = true;
|
||||
QString fmtEnumMember(const QString& name, int64_t value, int depth, int nameW) {
|
||||
QString ind = indent(depth);
|
||||
return ind + name.leftJustified(nameW) + QStringLiteral(" = ") + QString::number(value);
|
||||
}
|
||||
|
||||
while (pos < s.size()) {
|
||||
// Skip whitespace
|
||||
while (pos < s.size() && s[pos].isSpace()) pos++;
|
||||
if (pos >= s.size()) break;
|
||||
// ── Bitfield member formatting ──
|
||||
|
||||
// Check for +/- operator (except first term)
|
||||
if (!firstTerm) {
|
||||
if (s[pos] == '+' || s[pos] == '-') pos++;
|
||||
else return QStringLiteral("invalid '%1'").arg(s[pos]);
|
||||
while (pos < s.size() && s[pos].isSpace()) pos++;
|
||||
}
|
||||
|
||||
// Skip 0x prefix if present
|
||||
if (pos + 1 < s.size() && s[pos] == '0' && (s[pos+1] == 'x' || s[pos+1] == 'X'))
|
||||
pos += 2;
|
||||
|
||||
// Must have at least one hex digit
|
||||
int numStart = pos;
|
||||
while (pos < s.size() && (s[pos].isDigit() ||
|
||||
(s[pos] >= 'a' && s[pos] <= 'f') ||
|
||||
(s[pos] >= 'A' && s[pos] <= 'F'))) pos++;
|
||||
|
||||
if (pos == numStart) return QStringLiteral("invalid");
|
||||
|
||||
firstTerm = false;
|
||||
uint64_t extractBits(const Provider& prov, uint64_t addr,
|
||||
NodeKind containerKind,
|
||||
uint8_t bitOffset, uint8_t bitWidth) {
|
||||
uint64_t container = 0;
|
||||
switch (containerKind) {
|
||||
case NodeKind::Hex8: container = prov.readU8(addr); break;
|
||||
case NodeKind::Hex16: container = prov.readU16(addr); break;
|
||||
case NodeKind::Hex32: container = prov.readU32(addr); break;
|
||||
default: container = prov.readU64(addr); break;
|
||||
}
|
||||
Q_ASSERT(bitOffset + bitWidth <= 64);
|
||||
if (bitWidth >= 64) return container >> bitOffset;
|
||||
return (container >> bitOffset) & ((1ULL << bitWidth) - 1);
|
||||
}
|
||||
|
||||
return {};
|
||||
QString fmtBitfieldMember(const QString& name, uint8_t bitWidth,
|
||||
uint64_t value, int depth, int nameW) {
|
||||
QString ind = indent(depth);
|
||||
return ind + name.leftJustified(nameW)
|
||||
+ QStringLiteral(" : %1 = %2").arg(bitWidth).arg(value);
|
||||
}
|
||||
|
||||
} // namespace rcx::fmt
|
||||
|
||||
1430
src/generator.cpp
@@ -6,14 +6,82 @@
|
||||
|
||||
namespace rcx {
|
||||
|
||||
// Generate C++ struct definitions for a single root struct and all
|
||||
// nested/referenced types reachable from it.
|
||||
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr);
|
||||
// ── Code output format ──
|
||||
|
||||
// Generate C++ struct definitions for every root-level struct (full SDK).
|
||||
enum class CodeFormat : int {
|
||||
CppHeader = 0, // C/C++ struct definitions
|
||||
RustStruct, // Rust #[repr(C)] struct definitions
|
||||
DefineOffsets, // #define ClassName_FieldName 0xNN
|
||||
CSharpStruct, // C# [StructLayout] with [FieldOffset]
|
||||
PythonCtypes, // Python ctypes.Structure
|
||||
_Count
|
||||
};
|
||||
|
||||
enum class CodeScope : int {
|
||||
Current = 0, // Just the selected struct
|
||||
WithChildren, // Selected struct + all referenced types
|
||||
FullSdk, // All root-level structs
|
||||
_Count
|
||||
};
|
||||
|
||||
const char* codeFormatName(CodeFormat fmt);
|
||||
const char* codeFormatFileFilter(CodeFormat fmt);
|
||||
const char* codeScopeName(CodeScope scope);
|
||||
|
||||
// ── Format-aware dispatch (calls the appropriate backend) ──
|
||||
|
||||
QString renderCode(CodeFormat fmt, const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
// Render rootStructId + all struct types reachable from it
|
||||
QString renderCodeTree(CodeFormat fmt, const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
QString renderCodeAll(CodeFormat fmt, const NodeTree& tree,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
// ── Individual backends ──
|
||||
|
||||
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
QString renderCppTree(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
QString renderCppAll(const NodeTree& tree,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr);
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
QString renderRust(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
QString renderRustTree(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
QString renderRustAll(const NodeTree& tree,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
QString renderDefines(const NodeTree& tree, uint64_t rootStructId);
|
||||
QString renderDefinesTree(const NodeTree& tree, uint64_t rootStructId);
|
||||
QString renderDefinesAll(const NodeTree& tree);
|
||||
|
||||
QString renderCSharp(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
QString renderCSharpTree(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
QString renderCSharpAll(const NodeTree& tree,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
QString renderPython(const NodeTree& tree, uint64_t rootStructId);
|
||||
QString renderPythonTree(const NodeTree& tree, uint64_t rootStructId);
|
||||
QString renderPythonAll(const NodeTree& tree);
|
||||
|
||||
// Null generator placeholder (returns empty string).
|
||||
QString renderNull(const NodeTree& tree, uint64_t rootStructId);
|
||||
|
||||
BIN
src/icons/class.icns
Normal file
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
|
||||
1387
src/imports/import_pdb.cpp
Normal file
63
src/imports/import_pdb.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include "core.h"
|
||||
#include <QVector>
|
||||
#include <functional>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
// ── PDB Symbol Extraction ──
|
||||
|
||||
struct PdbSymbol {
|
||||
QString name;
|
||||
uint32_t rva;
|
||||
uint32_t typeIndex = 0; // TPI type index (0 = unknown / public symbol)
|
||||
};
|
||||
|
||||
struct PdbSymbolResult {
|
||||
QString moduleName; // derived from PDB filename (e.g. "ntoskrnl")
|
||||
QVector<PdbSymbol> symbols;
|
||||
};
|
||||
|
||||
// Extract public/global symbols (name → RVA) from a PDB file.
|
||||
// This reads the DBI stream's public and global symbol sub-streams.
|
||||
PdbSymbolResult extractPdbSymbols(const QString& pdbPath,
|
||||
QString* errorMsg = nullptr);
|
||||
|
||||
// ── PDB Type Import ──
|
||||
|
||||
struct PdbTypeInfo {
|
||||
uint32_t typeIndex; // TPI type index
|
||||
QString name; // struct/class/union/enum name
|
||||
uint64_t size; // sizeof in bytes
|
||||
int childCount; // direct member count
|
||||
bool isUnion; // union vs struct/class
|
||||
bool isEnum = false; // enum type
|
||||
};
|
||||
|
||||
// Phase 1: Enumerate all UDT types in the PDB (fast scan, no recursive import).
|
||||
QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath,
|
||||
QString* errorMsg = nullptr);
|
||||
|
||||
// Phase 2: Import selected types with full recursive child types.
|
||||
// progressCb is called with (current, total) for each top-level type;
|
||||
// return false from the callback to cancel the import.
|
||||
using ProgressCb = std::function<bool(int current, int total)>;
|
||||
NodeTree importPdbSelected(const QString& pdbPath,
|
||||
const QVector<uint32_t>& typeIndices,
|
||||
QString* errorMsg = nullptr,
|
||||
ProgressCb progressCb = {});
|
||||
|
||||
// Legacy single-call API: import one struct by name (or all if filter empty).
|
||||
NodeTree importPdb(const QString& pdbPath,
|
||||
const QString& structFilter = {},
|
||||
QString* errorMsg = nullptr);
|
||||
|
||||
// Import the type associated with a global symbol's typeIndex.
|
||||
// Opens the PDB, resolves the typeIndex to a UDT/enum, and returns the imported tree.
|
||||
// Returns empty tree if the symbol has no associated type or the type is a simple primitive.
|
||||
NodeTree importTypeForSymbol(const QString& pdbPath,
|
||||
uint32_t typeIndex,
|
||||
QString* typeName = nullptr,
|
||||
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.push_back(PendingRef{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.push_back(PendingRef{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.push_back(PendingRef{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
|
||||
1624
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
|
||||
193
src/imports/pe_debug_info.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "pe_debug_info.h"
|
||||
#include "../providers/provider.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
// Minimal PE structures (no Windows SDK dependency)
|
||||
#pragma pack(push, 1)
|
||||
struct DosHeader {
|
||||
uint16_t e_magic; // 'MZ'
|
||||
uint8_t pad[58];
|
||||
int32_t e_lfanew; // offset to PE signature
|
||||
};
|
||||
|
||||
struct CoffHeader {
|
||||
uint16_t Machine;
|
||||
uint16_t NumberOfSections;
|
||||
uint32_t TimeDateStamp;
|
||||
uint32_t PointerToSymbolTable;
|
||||
uint32_t NumberOfSymbols;
|
||||
uint16_t SizeOfOptionalHeader;
|
||||
uint16_t Characteristics;
|
||||
};
|
||||
|
||||
struct DataDirectory {
|
||||
uint32_t VirtualAddress;
|
||||
uint32_t Size;
|
||||
};
|
||||
|
||||
// Only the fields we need from the optional header
|
||||
struct OptionalHeader32 {
|
||||
uint16_t Magic; // 0x10b = PE32, 0x20b = PE32+
|
||||
uint8_t pad[90];
|
||||
uint32_t NumberOfRvaAndSizes;
|
||||
// DataDirectory[0] = Export, [1] = Import, ... [6] = Debug
|
||||
};
|
||||
|
||||
struct OptionalHeader64 {
|
||||
uint16_t Magic; // 0x20b = PE32+
|
||||
uint8_t pad[106];
|
||||
uint32_t NumberOfRvaAndSizes;
|
||||
};
|
||||
|
||||
struct DebugDirectory {
|
||||
uint32_t Characteristics;
|
||||
uint32_t TimeDateStamp;
|
||||
uint16_t MajorVersion;
|
||||
uint16_t MinorVersion;
|
||||
uint32_t Type;
|
||||
uint32_t SizeOfData;
|
||||
uint32_t AddressOfRawData; // RVA when loaded
|
||||
uint32_t PointerToRawData; // file offset (not used for memory reads)
|
||||
};
|
||||
|
||||
struct CvInfoPdb70 {
|
||||
uint32_t Signature; // 'RSDS'
|
||||
uint8_t Guid[16];
|
||||
uint32_t Age;
|
||||
// char PdbFileName[] follows
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static constexpr uint16_t kMZ = 0x5A4D;
|
||||
static constexpr uint32_t kPE = 0x00004550;
|
||||
static constexpr uint16_t kPE32 = 0x10b;
|
||||
static constexpr uint16_t kPE32P = 0x20b;
|
||||
static constexpr uint32_t kRSDS = 0x53445352;
|
||||
static constexpr uint32_t kDebugType_CodeView = 2;
|
||||
|
||||
static QString guidToString(const uint8_t guid[16]) {
|
||||
// Windows GUID is mixed-endian: Data1(4B LE), Data2(2B LE), Data3(2B LE), Data4(8B sequential)
|
||||
// MS symbol server expects native integer values for Data1/2/3, sequential for Data4
|
||||
uint32_t d1; memcpy(&d1, guid, 4);
|
||||
uint16_t d2; memcpy(&d2, guid + 4, 2);
|
||||
uint16_t d3; memcpy(&d3, guid + 6, 2);
|
||||
QString s = QStringLiteral("%1%2%3")
|
||||
.arg(d1, 8, 16, QLatin1Char('0'))
|
||||
.arg(d2, 4, 16, QLatin1Char('0'))
|
||||
.arg(d3, 4, 16, QLatin1Char('0'));
|
||||
for (int i = 8; i < 16; i++)
|
||||
s += QStringLiteral("%1").arg(guid[i], 2, 16, QLatin1Char('0'));
|
||||
return s.toUpper();
|
||||
}
|
||||
|
||||
PdbDebugInfo extractPdbDebugInfo(const Provider& prov, uint64_t moduleBase) {
|
||||
PdbDebugInfo result;
|
||||
|
||||
// Read DOS header
|
||||
DosHeader dos;
|
||||
if (!prov.read(moduleBase, &dos, sizeof(dos)))
|
||||
return result;
|
||||
if (dos.e_magic != kMZ)
|
||||
return result;
|
||||
|
||||
uint64_t peOffset = moduleBase + dos.e_lfanew;
|
||||
|
||||
// Read PE signature
|
||||
uint32_t peSig = 0;
|
||||
if (!prov.read(peOffset, &peSig, 4))
|
||||
return result;
|
||||
if (peSig != kPE)
|
||||
return result;
|
||||
|
||||
// Read COFF header
|
||||
uint64_t coffOffset = peOffset + 4;
|
||||
CoffHeader coff;
|
||||
if (!prov.read(coffOffset, &coff, sizeof(coff)))
|
||||
return result;
|
||||
|
||||
// Read optional header magic to determine PE32 vs PE32+
|
||||
uint64_t optOffset = coffOffset + sizeof(CoffHeader);
|
||||
uint16_t optMagic = 0;
|
||||
if (!prov.read(optOffset, &optMagic, 2))
|
||||
return result;
|
||||
|
||||
// Locate debug data directory (index 6)
|
||||
uint32_t numRvaAndSizes = 0;
|
||||
uint64_t dataDirsOffset = 0;
|
||||
|
||||
if (optMagic == kPE32) {
|
||||
// PE32: NumberOfRvaAndSizes at offset 92, data dirs at offset 96
|
||||
if (!prov.read(optOffset + 92, &numRvaAndSizes, 4))
|
||||
return result;
|
||||
dataDirsOffset = optOffset + 96;
|
||||
} else if (optMagic == kPE32P) {
|
||||
// PE32+: NumberOfRvaAndSizes at offset 108, data dirs at offset 112
|
||||
if (!prov.read(optOffset + 108, &numRvaAndSizes, 4))
|
||||
return result;
|
||||
dataDirsOffset = optOffset + 112;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (numRvaAndSizes <= 6)
|
||||
return result; // no debug directory
|
||||
|
||||
DataDirectory debugDir;
|
||||
if (!prov.read(dataDirsOffset + 6 * sizeof(DataDirectory), &debugDir, sizeof(debugDir)))
|
||||
return result;
|
||||
|
||||
if (debugDir.VirtualAddress == 0 || debugDir.Size == 0)
|
||||
return result;
|
||||
|
||||
// Read debug directory entries
|
||||
int numEntries = debugDir.Size / sizeof(DebugDirectory);
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
DebugDirectory entry;
|
||||
uint64_t entryAddr = moduleBase + debugDir.VirtualAddress + i * sizeof(DebugDirectory);
|
||||
if (!prov.read(entryAddr, &entry, sizeof(entry)))
|
||||
continue;
|
||||
|
||||
if (entry.Type != kDebugType_CodeView)
|
||||
continue;
|
||||
|
||||
// Read CodeView info (RSDS)
|
||||
if (entry.AddressOfRawData == 0 || entry.SizeOfData < sizeof(CvInfoPdb70) + 1)
|
||||
continue;
|
||||
|
||||
CvInfoPdb70 cv;
|
||||
uint64_t cvAddr = moduleBase + entry.AddressOfRawData;
|
||||
if (!prov.read(cvAddr, &cv, sizeof(cv)))
|
||||
continue;
|
||||
|
||||
if (cv.Signature != kRSDS)
|
||||
continue;
|
||||
|
||||
// Read PDB filename (null-terminated string after the struct)
|
||||
int nameMaxLen = entry.SizeOfData - sizeof(CvInfoPdb70);
|
||||
if (nameMaxLen > 260) nameMaxLen = 260;
|
||||
char nameBuf[261] = {};
|
||||
if (!prov.read(cvAddr + sizeof(CvInfoPdb70), nameBuf, nameMaxLen))
|
||||
continue;
|
||||
nameBuf[nameMaxLen] = '\0';
|
||||
|
||||
result.pdbName = QString::fromLatin1(nameBuf);
|
||||
// Extract just the filename if it contains a path
|
||||
int lastSlash = result.pdbName.lastIndexOf('\\');
|
||||
if (lastSlash >= 0)
|
||||
result.pdbName = result.pdbName.mid(lastSlash + 1);
|
||||
int lastFwdSlash = result.pdbName.lastIndexOf('/');
|
||||
if (lastFwdSlash >= 0)
|
||||
result.pdbName = result.pdbName.mid(lastFwdSlash + 1);
|
||||
|
||||
result.guidString = guidToString(cv.Guid);
|
||||
result.age = cv.Age;
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace rcx
|
||||
20
src/imports/pe_debug_info.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <cstdint>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
class Provider;
|
||||
|
||||
struct PdbDebugInfo {
|
||||
QString pdbName; // e.g. "ntoskrnl.pdb"
|
||||
QString guidString; // 32 hex chars, no dashes, uppercase
|
||||
uint32_t age = 0;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// Extract PDB debug info (GUID, age, filename) from a PE module in memory.
|
||||
// Reads DOS header → PE header → debug directory → CodeView RSDS record.
|
||||
PdbDebugInfo extractPdbDebugInfo(const Provider& prov, uint64_t moduleBase);
|
||||
|
||||
} // namespace rcx
|
||||
@@ -10,8 +10,9 @@
|
||||
#define RCX_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
// Forward declaration
|
||||
// Forward declarations
|
||||
namespace rcx { class Provider; }
|
||||
class QMenu;
|
||||
|
||||
/**
|
||||
* Plugin interface for Reclass
|
||||
@@ -66,7 +67,8 @@ struct PluginProcessInfo {
|
||||
QString name;
|
||||
QString path;
|
||||
QIcon icon;
|
||||
|
||||
bool is32Bit = false;
|
||||
|
||||
PluginProcessInfo() : pid(0) {}
|
||||
PluginProcessInfo(uint32_t p, const QString& n, const QString& pth = QString(), const QIcon& i = QIcon())
|
||||
: pid(p), name(n), path(pth), icon(i) {}
|
||||
@@ -128,6 +130,13 @@ public:
|
||||
* @return true if enumerateProcesses() should be called
|
||||
*/
|
||||
virtual bool providesProcessList() const { return false; }
|
||||
|
||||
/**
|
||||
* Add plugin-specific actions to the source menu (optional).
|
||||
* Called each time the source menu is shown. Only add items when relevant
|
||||
* (e.g., "Unload Driver" only when the driver is loaded).
|
||||
*/
|
||||
virtual void populatePluginMenu(QMenu*) {}
|
||||
};
|
||||
|
||||
// Plugin factory function signature
|
||||
|
||||
13
src/macos_titlebar.h
Normal file
@@ -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
|
||||
5419
src/main.cpp
163
src/mainwindow.h
@@ -2,31 +2,46 @@
|
||||
#include "controller.h"
|
||||
#include "titlebar.h"
|
||||
#include "pluginmanager.h"
|
||||
#include "scannerpanel.h"
|
||||
#include "imports/import_pdb.h"
|
||||
#include "startpage.h"
|
||||
#include "workspace_model.h"
|
||||
namespace rcx { class SymbolDownloader; }
|
||||
#include <QMainWindow>
|
||||
#include <QMdiArea>
|
||||
#include <QMdiSubWindow>
|
||||
#include <QLabel>
|
||||
#include <QSplitter>
|
||||
#include <QTabWidget>
|
||||
#include <QDockWidget>
|
||||
#include <QTreeView>
|
||||
#include <QStandardItemModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QLineEdit>
|
||||
#include <QMap>
|
||||
#include <QButtonGroup>
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
#include <Qsci/qsciscintilla.h>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
class McpBridge;
|
||||
class ShimmerLabel;
|
||||
class DockGripWidget;
|
||||
class WorkspaceDelegate;
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
friend class McpBridge;
|
||||
public:
|
||||
explicit MainWindow(QWidget* parent = nullptr);
|
||||
~MainWindow() override;
|
||||
|
||||
private slots:
|
||||
void newFile();
|
||||
void newDocument();
|
||||
void newClass();
|
||||
void newStruct();
|
||||
void newEnum();
|
||||
void selfTest();
|
||||
void openFile();
|
||||
void saveFile();
|
||||
@@ -47,32 +62,62 @@ private slots:
|
||||
void toggleMcp();
|
||||
void setEditorFont(const QString& fontName);
|
||||
void exportCpp();
|
||||
void exportRust();
|
||||
void exportDefines();
|
||||
void exportCSharp();
|
||||
void exportPython();
|
||||
void exportReclassXmlAction();
|
||||
void importFromSource();
|
||||
void importReclassXml();
|
||||
void importPdb();
|
||||
void showTypeAliasesDialog();
|
||||
void editTheme();
|
||||
void showOptionsDialog();
|
||||
void showOptionsDialog(int initialPage);
|
||||
|
||||
public:
|
||||
// Status bar helpers — separate app / MCP channels
|
||||
void setAppStatus(const QString& text);
|
||||
void setAppStatus(const QString& text, const QString& dimSuffix);
|
||||
void setMcpStatus(const QString& text);
|
||||
void clearMcpStatus();
|
||||
|
||||
// Project Lifecycle API
|
||||
QMdiSubWindow* project_new();
|
||||
QMdiSubWindow* project_open(const QString& path = {});
|
||||
bool project_save(QMdiSubWindow* sub = nullptr, bool saveAs = false);
|
||||
void project_close(QMdiSubWindow* sub = nullptr);
|
||||
QDockWidget* project_new(const QString& classKeyword = QString());
|
||||
QDockWidget* project_open(const QString& path = {});
|
||||
bool project_save(QDockWidget* dock = nullptr, bool saveAs = false);
|
||||
void project_close(QDockWidget* dock = nullptr);
|
||||
|
||||
private:
|
||||
enum ViewMode { VM_Reclass, VM_Rendered };
|
||||
|
||||
QMdiArea* m_mdiArea;
|
||||
QLabel* m_statusLabel;
|
||||
QWidget* m_centralPlaceholder;
|
||||
ShimmerLabel* m_statusLabel;
|
||||
QString m_appStatus;
|
||||
QString m_appStatusDim;
|
||||
bool m_mcpBusy = false;
|
||||
QTimer* m_mcpClearTimer = nullptr;
|
||||
TitleBarWidget* m_titleBar = nullptr;
|
||||
QMenuBar* m_menuBar = nullptr;
|
||||
bool m_menuBarTitleCase = false;
|
||||
QWidget* m_borderOverlay = nullptr;
|
||||
PluginManager m_pluginManager;
|
||||
McpBridge* m_mcp = nullptr;
|
||||
QAction* m_mcpAction = nullptr;
|
||||
QAction* m_actRelOfs = nullptr;
|
||||
QMenu* m_sourceMenu = nullptr;
|
||||
QMenu* m_recentFilesMenu = nullptr;
|
||||
|
||||
struct SplitPane {
|
||||
QTabWidget* tabWidget = nullptr;
|
||||
RcxEditor* editor = nullptr;
|
||||
QsciScintilla* rendered = nullptr;
|
||||
QLineEdit* findBar = nullptr;
|
||||
QWidget* findContainer = nullptr;
|
||||
QWidget* renderedContainer = nullptr;
|
||||
QComboBox* fmtCombo = nullptr;
|
||||
QComboBox* scopeCombo = nullptr;
|
||||
QToolButton* fmtGear = nullptr;
|
||||
ViewMode viewMode = VM_Reclass;
|
||||
uint64_t lastRenderedRootId = 0;
|
||||
};
|
||||
@@ -84,20 +129,38 @@ private:
|
||||
QVector<SplitPane> panes;
|
||||
int activePaneIdx = 0;
|
||||
};
|
||||
QMap<QMdiSubWindow*, TabState> m_tabs;
|
||||
|
||||
QMap<QDockWidget*, TabState> m_tabs;
|
||||
QVector<QDockWidget*> m_docDocks; // ordered list for tabByIndex
|
||||
QDockWidget* m_activeDocDock = nullptr; // tracks active document dock
|
||||
QVector<QDockWidget*> m_sentinelDocks; // permanent sentinels for always-visible tab bars
|
||||
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
|
||||
bool m_closingAll = false; // guards spurious project_new during batch close
|
||||
struct ClosingGuard {
|
||||
bool& flag;
|
||||
ClosingGuard(bool& f) : flag(f) { flag = true; }
|
||||
~ClosingGuard() { flag = false; }
|
||||
};
|
||||
void rebuildAllDocs();
|
||||
|
||||
void createMenus();
|
||||
void applyMenuBarTitleCase(bool titleCase);
|
||||
void createStatusBar();
|
||||
void showPluginsDialog();
|
||||
void populateSourceMenu();
|
||||
void addRecentFile(const QString& path);
|
||||
void updateRecentFilesMenu();
|
||||
QIcon makeIcon(const QString& svgPath);
|
||||
|
||||
RcxController* activeController() const;
|
||||
TabState* activeTab();
|
||||
TabState* tabByIndex(int index);
|
||||
int tabCount() const { return m_tabs.size(); }
|
||||
QMdiSubWindow* createTab(RcxDocument* doc);
|
||||
QDockWidget* createSentinelDock();
|
||||
QDockWidget* createTab(RcxDocument* doc);
|
||||
QString tabTitle(const TabState& tab) const;
|
||||
void setupDockTabBars();
|
||||
void updateWindowTitle();
|
||||
void closeAllDocDocks();
|
||||
|
||||
void setViewMode(ViewMode mode);
|
||||
void updateRenderedView(TabState& tab, SplitPane& pane);
|
||||
@@ -107,23 +170,85 @@ private:
|
||||
|
||||
SplitPane createSplitPane(TabState& tab);
|
||||
void applyTheme(const Theme& theme);
|
||||
void applyTabWidgetStyle(QTabWidget* tw);
|
||||
void styleTabCloseButtons();
|
||||
void syncViewButtons(ViewMode mode);
|
||||
SplitPane* findPaneByTabWidget(QTabWidget* tw);
|
||||
SplitPane* findActiveSplitPane();
|
||||
RcxEditor* activePaneEditor();
|
||||
|
||||
// Workspace dock
|
||||
QDockWidget* m_workspaceDock = nullptr;
|
||||
QTreeView* m_workspaceTree = nullptr;
|
||||
QStandardItemModel* m_workspaceModel = nullptr;
|
||||
QDockWidget* m_workspaceDock = nullptr;
|
||||
QTreeView* m_workspaceTree = nullptr;
|
||||
QStandardItemModel* m_workspaceModel = nullptr;
|
||||
QSortFilterProxyModel* m_workspaceProxy = nullptr;
|
||||
QLineEdit* m_workspaceSearch = nullptr;
|
||||
WorkspaceDelegate* m_workspaceDelegate = nullptr;
|
||||
QLabel* m_dockTitleLabel = nullptr;
|
||||
QToolButton* m_dockCloseBtn = nullptr;
|
||||
DockGripWidget* m_dockGrip = nullptr;
|
||||
QSet<uint64_t> m_pinnedIds;
|
||||
void createWorkspaceDock();
|
||||
void rebuildWorkspaceModel();
|
||||
void rebuildWorkspaceModel(); // debounced — safe to call frequently
|
||||
void rebuildWorkspaceModelNow(); // immediate rebuild
|
||||
QTimer* m_workspaceRebuildTimer = nullptr;
|
||||
QTimer* m_workspaceSearchTimer = nullptr;
|
||||
void updateBorderColor(const QColor& color);
|
||||
|
||||
// Scanner dock
|
||||
QDockWidget* m_scannerDock = nullptr;
|
||||
ScannerPanel* m_scannerPanel = nullptr;
|
||||
QLabel* m_scanDockTitle = nullptr;
|
||||
QToolButton* m_scanDockCloseBtn = nullptr;
|
||||
DockGripWidget* m_scanDockGrip = nullptr;
|
||||
void createScannerDock();
|
||||
|
||||
// Modules/Symbols dock
|
||||
QDockWidget* m_symbolsDock = nullptr;
|
||||
QTabWidget* m_symTabWidget = nullptr;
|
||||
// Modules tab
|
||||
QTreeView* m_modulesTree = nullptr;
|
||||
QStandardItemModel* m_modulesModel = nullptr;
|
||||
// Symbols tab
|
||||
QTreeView* m_symbolsTree = nullptr;
|
||||
QStandardItemModel* m_symbolsModel = nullptr;
|
||||
QSortFilterProxyModel* m_symbolsProxy = nullptr;
|
||||
QLineEdit* m_symbolsSearch = nullptr;
|
||||
// Title bar
|
||||
QLabel* m_symDockTitle = nullptr;
|
||||
QToolButton* m_symDockCloseBtn = nullptr;
|
||||
QToolButton* m_symDownloadBtn = nullptr;
|
||||
DockGripWidget* m_symDockGrip = nullptr;
|
||||
rcx::SymbolDownloader* m_symDownloader = nullptr;
|
||||
// Types tab
|
||||
QTreeView* m_typesTree = nullptr;
|
||||
QStandardItemModel* m_typesModel = nullptr;
|
||||
QSortFilterProxyModel* m_typesProxy = nullptr;
|
||||
QLineEdit* m_typesSearch = nullptr;
|
||||
QPushButton* m_typesImportBtn = nullptr;
|
||||
struct CachedModuleTypes {
|
||||
QString pdbPath;
|
||||
QVector<rcx::PdbTypeInfo> types;
|
||||
};
|
||||
QHash<QString, CachedModuleTypes> m_cachedModuleTypes;
|
||||
|
||||
void createSymbolsDock();
|
||||
void rebuildSymbolsModel();
|
||||
void rebuildTypesModel();
|
||||
void populateTypesModuleItem(QStandardItem* moduleItem);
|
||||
void rebuildModulesModel();
|
||||
void importSelectedTypes();
|
||||
void downloadSymbolsForProcess();
|
||||
// Load PDB symbols + typeIndices into SymbolStore, cache types. Returns symbol count.
|
||||
int loadPdbAndCacheTypes(const QString& pdbPath);
|
||||
|
||||
// Start page
|
||||
StartPageWidget* m_startPage = nullptr;
|
||||
Q_INVOKABLE void showStartPage();
|
||||
void dismissStartPage();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
};
|
||||
|
||||
} // namespace rcx
|
||||
|
||||