mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Merge remote-tracking branch 'origin/linux'
This commit is contained in:
@@ -45,9 +45,10 @@ target_link_libraries(ReclassX PRIVATE
|
|||||||
Qt6::Svg
|
Qt6::Svg
|
||||||
Qt6::Concurrent
|
Qt6::Concurrent
|
||||||
QScintilla::QScintilla
|
QScintilla::QScintilla
|
||||||
dbghelp
|
|
||||||
psapi
|
|
||||||
)
|
)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(ReclassX PRIVATE dbghelp psapi)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_target(screenshot ALL
|
add_custom_target(screenshot ALL
|
||||||
COMMAND ReclassX --screenshot ${CMAKE_BINARY_DIR}/screenshot.png
|
COMMAND ReclassX --screenshot ${CMAKE_BINARY_DIR}/screenshot.png
|
||||||
@@ -143,7 +144,10 @@ if(BUILD_TESTING)
|
|||||||
target_include_directories(test_controller PRIVATE src)
|
target_include_directories(test_controller PRIVATE src)
|
||||||
target_link_libraries(test_controller PRIVATE
|
target_link_libraries(test_controller PRIVATE
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
QScintilla::QScintilla)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(test_controller PRIVATE dbghelp psapi)
|
||||||
|
endif()
|
||||||
add_test(NAME test_controller COMMAND test_controller)
|
add_test(NAME test_controller COMMAND test_controller)
|
||||||
|
|
||||||
add_executable(test_validation tests/test_validation.cpp
|
add_executable(test_validation tests/test_validation.cpp
|
||||||
@@ -153,7 +157,10 @@ if(BUILD_TESTING)
|
|||||||
target_include_directories(test_validation PRIVATE src)
|
target_include_directories(test_validation PRIVATE src)
|
||||||
target_link_libraries(test_validation PRIVATE
|
target_link_libraries(test_validation PRIVATE
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
QScintilla::QScintilla)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(test_validation PRIVATE dbghelp psapi)
|
||||||
|
endif()
|
||||||
add_test(NAME test_validation COMMAND test_validation)
|
add_test(NAME test_validation COMMAND test_validation)
|
||||||
|
|
||||||
add_executable(test_generator tests/test_generator.cpp
|
add_executable(test_generator tests/test_generator.cpp
|
||||||
@@ -169,7 +176,10 @@ if(BUILD_TESTING)
|
|||||||
target_include_directories(test_context_menu PRIVATE src)
|
target_include_directories(test_context_menu PRIVATE src)
|
||||||
target_link_libraries(test_context_menu PRIVATE
|
target_link_libraries(test_context_menu PRIVATE
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
QScintilla::QScintilla)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(test_context_menu PRIVATE dbghelp psapi)
|
||||||
|
endif()
|
||||||
add_test(NAME test_context_menu COMMAND test_context_menu)
|
add_test(NAME test_context_menu COMMAND test_context_menu)
|
||||||
|
|
||||||
add_executable(test_rendered_view tests/test_rendered_view.cpp
|
add_executable(test_rendered_view tests/test_rendered_view.cpp
|
||||||
@@ -187,7 +197,10 @@ if(BUILD_TESTING)
|
|||||||
target_include_directories(test_new_features PRIVATE src)
|
target_include_directories(test_new_features PRIVATE src)
|
||||||
target_link_libraries(test_new_features PRIVATE
|
target_link_libraries(test_new_features PRIVATE
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||||
QScintilla::QScintilla dbghelp psapi)
|
QScintilla::QScintilla)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(test_new_features PRIVATE dbghelp psapi)
|
||||||
|
endif()
|
||||||
add_test(NAME test_new_features COMMAND test_new_features)
|
add_test(NAME test_new_features COMMAND test_new_features)
|
||||||
|
|
||||||
add_executable(test_type_selector tests/test_type_selector.cpp
|
add_executable(test_type_selector tests/test_type_selector.cpp
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
This tool helps you inspect raw bytes,and interpret those bytes as (int,float,struct, array, etc) instead of just hex. it supports ptrs/arrays so you can see the relationship between data. And help deduce paths to these classes from long pointer chains. Either modify the values inside the editor or export as .h to reuse these structures in your code.
|
This tool helps you inspect raw bytes and interpret those as (int, float, struct, array, etc.) instead of just hex. It supports ptrs/arrays so you can see the relationship between data. And help deduce paths to these classes from long pointer chains. Either modify the values inside the editor or export as .h to reuse these structures in your code.
|
||||||
|
|
||||||
Video: https://github.com/IChooseYou/ReclassX/raw/main/video.mp4
|
Video: https://github.com/IChooseYou/ReclassX/raw/main/video.mp4
|
||||||

|

|
||||||
|
|
||||||
QScintilla (github: https://github.com/brCreate/QScintilla) happens to be a really nice fit for our project.
|
QScintilla (github: https://github.com/brCreate/QScintilla) happens to be a really nice fit for our project.
|
||||||
We can treat parts of a line as a spans. It's very easy to implement inline editing. Additionally the syntax coloring and keyboard navigation were very useful also!
|
We can treat parts of a line as a spans. It is very easy to implement inline editing. Additionally the syntax coloring and keyboard navigation were very useful also!
|
||||||
|
|
||||||
We hit some big milestones (first plugin+first linux build) however project is still currently a work in progress. Mainly developed/tested on windows (for now).
|
We hit some big milestones (plugin support with a first POC plugihn, first working linux build) however project is still currently a work in progress. Mainly developed/tested on windows (for now).
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ We hit some big milestones (first plugin+first linux build) however project is s
|
|||||||
cd ReclassX
|
cd ReclassX
|
||||||
.\scripts\build_qscintilla.ps1
|
.\scripts\build_qscintilla.ps1
|
||||||
.\scripts\build.ps1
|
.\scripts\build.ps1
|
||||||
^ script above tries to detect autodetect Qt install (as we learned not every installs to C)
|
^ script above tries to autodetect Qt install (as we learned not everyone installs to C:/Qt/)
|
||||||
|
|
||||||
3. Manual Build
|
3. Manual Build
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,18 @@ add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
|||||||
# Link Qt
|
# Link Qt
|
||||||
target_link_libraries(ProcessMemoryPlugin PRIVATE Qt6::Widgets)
|
target_link_libraries(ProcessMemoryPlugin PRIVATE Qt6::Widgets)
|
||||||
|
|
||||||
|
# Platform-specific linking
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(ProcessMemoryPlugin PRIVATE psapi shell32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# On Linux, hide all symbols by default so only RCX_PLUGIN_EXPORT-marked ones are exported
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
target_compile_options(ProcessMemoryPlugin PRIVATE -fvisibility=hidden)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(ProcessMemoryPlugin PRIVATE
|
target_include_directories(ProcessMemoryPlugin PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,40 @@
|
|||||||
#include "ProcessMemoryPlugin.h"
|
#include "ProcessMemoryPlugin.h"
|
||||||
|
|
||||||
#include "../../src/processpicker.h"
|
#include "../../src/processpicker.h"
|
||||||
|
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <climits>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
|
#endif
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// ProcessMemoryProvider implementation
|
// ProcessMemoryProvider implementation
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processName)
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||||
: m_handle(nullptr)
|
: m_handle(nullptr)
|
||||||
, m_pid(pid)
|
, m_pid(pid)
|
||||||
, m_processName(processName)
|
, m_processName(processName)
|
||||||
@@ -19,7 +42,7 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa
|
|||||||
, m_base(0)
|
, m_base(0)
|
||||||
{
|
{
|
||||||
// Try to open with write access first
|
// Try to open with write access first
|
||||||
m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
|
m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
|
||||||
FALSE, pid);
|
FALSE, pid);
|
||||||
if (m_handle)
|
if (m_handle)
|
||||||
m_writable = true;
|
m_writable = true;
|
||||||
@@ -31,21 +54,13 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_handle)
|
if (m_handle)
|
||||||
{
|
|
||||||
cacheModules();
|
cacheModules();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessMemoryProvider::~ProcessMemoryProvider()
|
|
||||||
{
|
|
||||||
if (m_handle)
|
|
||||||
CloseHandle(m_handle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||||
{
|
{
|
||||||
if (!m_handle || len <= 0) return false;
|
if (!m_handle || len <= 0) return false;
|
||||||
|
|
||||||
SIZE_T bytesRead = 0;
|
SIZE_T bytesRead = 0;
|
||||||
if (ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead))
|
if (ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead))
|
||||||
return bytesRead == (SIZE_T)len;
|
return bytesRead == (SIZE_T)len;
|
||||||
@@ -55,7 +70,7 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
|||||||
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
{
|
{
|
||||||
if (!m_handle || !m_writable || len <= 0) return false;
|
if (!m_handle || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
SIZE_T bytesWritten = 0;
|
SIZE_T bytesWritten = 0;
|
||||||
if (WriteProcessMemory(m_handle, (LPVOID)(m_base + addr), buf, (SIZE_T)len, &bytesWritten))
|
if (WriteProcessMemory(m_handle, (LPVOID)(m_base + addr), buf, (SIZE_T)len, &bytesWritten))
|
||||||
return bytesWritten == (SIZE_T)len;
|
return bytesWritten == (SIZE_T)len;
|
||||||
@@ -64,9 +79,16 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
|||||||
|
|
||||||
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
{
|
{
|
||||||
// TODO: Implement module enumeration with EnumProcessModules
|
for (const auto& mod : m_modules)
|
||||||
// For now, just return empty (no symbol resolution)
|
{
|
||||||
Q_UNUSED(addr);
|
if (addr >= mod.base && addr < mod.base + mod.size)
|
||||||
|
{
|
||||||
|
uint64_t offset = addr - mod.base;
|
||||||
|
return QStringLiteral("%1+0x%2")
|
||||||
|
.arg(mod.name)
|
||||||
|
.arg(offset, 0, 16, QChar('0'));
|
||||||
|
}
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +120,190 @@ void ProcessMemoryProvider::cacheModules()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||||
|
: m_fd(-1)
|
||||||
|
, m_pid(pid)
|
||||||
|
, m_processName(processName)
|
||||||
|
, m_writable(false)
|
||||||
|
, m_base(0)
|
||||||
|
{
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
QByteArray pathUtf8 = memPath.toUtf8();
|
||||||
|
|
||||||
|
// Try read-write first
|
||||||
|
m_fd = ::open(pathUtf8.constData(), O_RDWR);
|
||||||
|
if (m_fd >= 0)
|
||||||
|
m_writable = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fall back to read-only
|
||||||
|
m_fd = ::open(pathUtf8.constData(), O_RDONLY);
|
||||||
|
m_writable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fd >= 0)
|
||||||
|
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_len = static_cast<size_t>(len);
|
||||||
|
|
||||||
|
ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0);
|
||||||
|
if (nread == static_cast<ssize_t>(len))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Fallback: pread on /proc/<pid>/mem
|
||||||
|
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
|
||||||
|
return nread == static_cast<ssize_t>(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
|
{
|
||||||
|
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
|
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_len = static_cast<size_t>(len);
|
||||||
|
|
||||||
|
ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0);
|
||||||
|
if (nwritten == static_cast<ssize_t>(len))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Fallback: pwrite on /proc/<pid>/mem
|
||||||
|
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
|
||||||
|
return nwritten == static_cast<ssize_t>(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
|
{
|
||||||
|
for (const auto& mod : m_modules)
|
||||||
|
{
|
||||||
|
if (addr >= mod.base && addr < mod.base + mod.size)
|
||||||
|
{
|
||||||
|
uint64_t offset = addr - mod.base;
|
||||||
|
return QStringLiteral("%1+0x%2")
|
||||||
|
.arg(mod.name)
|
||||||
|
.arg(offset, 0, 16, QChar('0'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessMemoryProvider::cacheModules()
|
||||||
|
{
|
||||||
|
// Parse /proc/<pid>/maps to discover loaded modules
|
||||||
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
||||||
|
std::ifstream mapsFile(mapsPath.toStdString());
|
||||||
|
if (!mapsFile.is_open()) return;
|
||||||
|
|
||||||
|
// Accumulate base/end per path, then convert to ModuleInfo
|
||||||
|
struct Range { uint64_t base; uint64_t end; };
|
||||||
|
QMap<QString, Range> moduleRanges;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
bool firstExec = true;
|
||||||
|
while (std::getline(mapsFile, line))
|
||||||
|
{
|
||||||
|
// Format: addr_start-addr_end perms offset dev inode pathname
|
||||||
|
// Example: 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/foo
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string addrRange, perms, offset, dev, inode, pathname;
|
||||||
|
iss >> addrRange >> perms >> offset >> dev >> inode;
|
||||||
|
std::getline(iss, pathname);
|
||||||
|
|
||||||
|
// Trim leading whitespace from pathname
|
||||||
|
size_t start = pathname.find_first_not_of(" \t");
|
||||||
|
if (start == std::string::npos) continue;
|
||||||
|
pathname = pathname.substr(start);
|
||||||
|
|
||||||
|
// Skip non-file mappings
|
||||||
|
if (pathname.empty() || pathname[0] != '/') continue;
|
||||||
|
// Skip special mappings
|
||||||
|
if (pathname.find("/dev/") == 0 || pathname.find("/memfd:") == 0) continue;
|
||||||
|
|
||||||
|
// Parse address range
|
||||||
|
auto dash = addrRange.find('-');
|
||||||
|
if (dash == std::string::npos) continue;
|
||||||
|
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||||
|
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
|
||||||
|
|
||||||
|
QString qpath = QString::fromStdString(pathname);
|
||||||
|
|
||||||
|
// Track first executable mapping as the base address
|
||||||
|
if (firstExec && perms.size() >= 3 && perms[2] == 'x')
|
||||||
|
{
|
||||||
|
m_base = addrStart;
|
||||||
|
firstExec = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = moduleRanges.find(qpath);
|
||||||
|
if (it != moduleRanges.end())
|
||||||
|
{
|
||||||
|
if (addrStart < it->base) it->base = addrStart;
|
||||||
|
if (addrEnd > it->end) it->end = addrEnd;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moduleRanges.insert(qpath, {addrStart, addrEnd});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_modules.reserve(moduleRanges.size());
|
||||||
|
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
||||||
|
{
|
||||||
|
QFileInfo fi(it.key());
|
||||||
|
m_modules.append({
|
||||||
|
fi.fileName(),
|
||||||
|
it->base,
|
||||||
|
it->end - it->base
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // platform
|
||||||
|
|
||||||
|
ProcessMemoryProvider::~ProcessMemoryProvider()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (m_handle)
|
||||||
|
CloseHandle(m_handle);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
if (m_fd >= 0)
|
||||||
|
::close(m_fd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProcessMemoryProvider::size() const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return m_handle ? INT_MAX : 0;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return m_fd ? INT_MAX : 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// ProcessMemoryPlugin implementation
|
// ProcessMemoryPlugin implementation
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
@@ -119,27 +325,26 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
|||||||
// Parse target: "pid:name" or just "pid"
|
// Parse target: "pid:name" or just "pid"
|
||||||
QStringList parts = target.split(':');
|
QStringList parts = target.split(':');
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
DWORD pid = parts[0].toUInt(&ok);
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
|
||||||
if (!ok || pid == 0) {
|
if (!ok || pid == 0)
|
||||||
|
{
|
||||||
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid);
|
QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid);
|
||||||
|
|
||||||
auto provider = std::make_unique<ProcessMemoryProvider>(pid, name);
|
auto provider = std::make_unique<ProcessMemoryProvider>(pid, name);
|
||||||
if (!provider->isValid())
|
if (!provider->isValid())
|
||||||
{
|
{
|
||||||
if (errorMsg)
|
if (errorMsg)
|
||||||
{
|
|
||||||
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
||||||
"Ensure the process is running and you have sufficient permissions.")
|
"Ensure the process is running and you have sufficient permissions.")
|
||||||
.arg(name).arg(pid);
|
.arg(name).arg(pid);
|
||||||
}
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,26 +356,49 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
|||||||
bool ok = false;
|
bool ok = false;
|
||||||
DWORD pid = parts[0].toUInt(&ok);
|
DWORD pid = parts[0].toUInt(&ok);
|
||||||
if (!ok || pid == 0) return 0;
|
if (!ok || pid == 0) return 0;
|
||||||
|
|
||||||
// Open process to get main module base
|
// Open process to get main module base
|
||||||
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
||||||
if (!hProc) return 0;
|
if (!hProc) return 0;
|
||||||
|
|
||||||
uint64_t base = 0;
|
uint64_t base = 0;
|
||||||
HMODULE hMod = nullptr;
|
HMODULE hMod = nullptr;
|
||||||
DWORD needed = 0;
|
DWORD needed = 0;
|
||||||
|
|
||||||
if (EnumProcessModulesEx(hProc, &hMod, sizeof(hMod), &needed, LIST_MODULES_ALL) && hMod)
|
if (EnumProcessModulesEx(hProc, &hMod, sizeof(hMod), &needed, LIST_MODULES_ALL) && hMod)
|
||||||
{
|
{
|
||||||
MODULEINFO mi{};
|
MODULEINFO mi{};
|
||||||
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
||||||
{
|
|
||||||
base = (uint64_t)mi.lpBaseOfDll;
|
base = (uint64_t)mi.lpBaseOfDll;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(hProc);
|
CloseHandle(hProc);
|
||||||
return base;
|
return base;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
// Parse PID from target
|
||||||
|
QStringList parts = target.split(':');
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) return 0;
|
||||||
|
|
||||||
|
// Find first executable mapping from /proc/<pid>/maps
|
||||||
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(pid);
|
||||||
|
std::ifstream mapsFile(mapsPath.toStdString());
|
||||||
|
if (!mapsFile.is_open()) return 0;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(mapsFile, line)) {
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string addrRange, perms;
|
||||||
|
iss >> addrRange >> perms;
|
||||||
|
if (perms.size() >= 3 && perms[2] == 'x') {
|
||||||
|
auto dash = addrRange.find('-');
|
||||||
|
if (dash != std::string::npos) {
|
||||||
|
return std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
#else
|
#else
|
||||||
Q_UNUSED(target);
|
Q_UNUSED(target);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -181,7 +409,7 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
{
|
{
|
||||||
// Use custom process enumeration from plugin
|
// Use custom process enumeration from plugin
|
||||||
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
||||||
|
|
||||||
// Convert to ProcessInfo for ProcessPicker
|
// Convert to ProcessInfo for ProcessPicker
|
||||||
QList<ProcessInfo> processes;
|
QList<ProcessInfo> processes;
|
||||||
for (const auto& pinfo : pluginProcesses)
|
for (const auto& pinfo : pluginProcesses)
|
||||||
@@ -193,50 +421,50 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
info.icon = pinfo.icon;
|
info.icon = pinfo.icon;
|
||||||
processes.append(info);
|
processes.append(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show ProcessPicker with custom process list
|
// Show ProcessPicker with custom process list
|
||||||
ProcessPicker picker(processes, parent);
|
ProcessPicker picker(processes, parent);
|
||||||
if (picker.exec() == QDialog::Accepted) {
|
if (picker.exec() == QDialog::Accepted) {
|
||||||
uint32_t pid = picker.selectedProcessId();
|
uint32_t pid = picker.selectedProcessId();
|
||||||
QString name = picker.selectedProcessName();
|
QString name = picker.selectedProcessName();
|
||||||
|
|
||||||
// Format target as "pid:name"
|
// Format target as "pid:name"
|
||||||
*target = QString("%1:%2").arg(pid).arg(name);
|
*target = QString("%1:%2").arg(pid).arg(name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||||
{
|
{
|
||||||
QVector<PluginProcessInfo> processes;
|
QVector<PluginProcessInfo> processes;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
if (snapshot == INVALID_HANDLE_VALUE) {
|
if (snapshot == INVALID_HANDLE_VALUE) {
|
||||||
return processes;
|
return processes;
|
||||||
}
|
}
|
||||||
|
|
||||||
PROCESSENTRY32W entry;
|
PROCESSENTRY32W entry;
|
||||||
entry.dwSize = sizeof(entry);
|
entry.dwSize = sizeof(entry);
|
||||||
|
|
||||||
if (Process32FirstW(snapshot, &entry)) {
|
if (Process32FirstW(snapshot, &entry)) {
|
||||||
do {
|
do {
|
||||||
PluginProcessInfo info;
|
PluginProcessInfo info;
|
||||||
info.pid = entry.th32ProcessID;
|
info.pid = entry.th32ProcessID;
|
||||||
info.name = QString::fromWCharArray(entry.szExeFile);
|
info.name = QString::fromWCharArray(entry.szExeFile);
|
||||||
|
|
||||||
// Try to get full path and icon
|
// Try to get full path and icon
|
||||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
|
||||||
if (hProcess) {
|
if (hProcess) {
|
||||||
wchar_t path[MAX_PATH * 2];
|
wchar_t path[MAX_PATH * 2];
|
||||||
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
|
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
|
||||||
|
|
||||||
// Try QueryFullProcessImageNameW first
|
// Try QueryFullProcessImageNameW first
|
||||||
if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) {
|
if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) {
|
||||||
info.path = QString::fromWCharArray(path);
|
info.path = QString::fromWCharArray(path);
|
||||||
|
|
||||||
// Extract icon
|
// Extract icon
|
||||||
SHFILEINFOW sfi = {};
|
SHFILEINFOW sfi = {};
|
||||||
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
||||||
@@ -247,18 +475,57 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(hProcess);
|
CloseHandle(hProcess);
|
||||||
}
|
}
|
||||||
|
|
||||||
processes.append(info);
|
processes.append(info);
|
||||||
|
|
||||||
} while (Process32NextW(snapshot, &entry));
|
} while (Process32NextW(snapshot, &entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
QDir procDir("/proc");
|
||||||
|
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
|
|
||||||
|
for (const QString& entry : entries) {
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = entry.toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) continue;
|
||||||
|
|
||||||
|
// Read process name from /proc/<pid>/comm
|
||||||
|
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||||
|
QFile commFile(commPath);
|
||||||
|
QString procName;
|
||||||
|
if (commFile.open(QIODevice::ReadOnly)) {
|
||||||
|
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||||
|
commFile.close();
|
||||||
|
}
|
||||||
|
if (procName.isEmpty()) continue; // Skip kernel threads with no name
|
||||||
|
|
||||||
|
// Read exe path from /proc/<pid>/exe symlink
|
||||||
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||||
|
QFileInfo exeInfo(exePath);
|
||||||
|
QString resolvedPath;
|
||||||
|
if (exeInfo.exists())
|
||||||
|
resolvedPath = exeInfo.symLinkTarget();
|
||||||
|
|
||||||
|
// Skip if we can't read the process memory (no access)
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PluginProcessInfo info;
|
||||||
|
info.pid = pid;
|
||||||
|
info.name = procName;
|
||||||
|
info.path = resolvedPath;
|
||||||
|
info.icon = defaultIcon;
|
||||||
|
processes.append(info);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return processes;
|
return processes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +533,7 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|||||||
// Plugin factory
|
// Plugin factory
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) IPlugin* CreatePlugin()
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||||
{
|
{
|
||||||
return new ProcessMemoryPlugin();
|
return new ProcessMemoryPlugin();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../../src/iplugin.h"
|
#include "../../src/iplugin.h"
|
||||||
#include "../../src/core.h"
|
#include "../../src/core.h"
|
||||||
#include <windows.h>
|
|
||||||
#include <tlhelp32.h>
|
#include <cstdint>
|
||||||
#include <psapi.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Windows process memory provider
|
* Process memory provider
|
||||||
* Reads/writes memory from a live process using Win32 API
|
* Reads/writes memory from a live process using platform APIs
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryProvider : public rcx::Provider {
|
class ProcessMemoryProvider : public rcx::Provider
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ProcessMemoryProvider(DWORD pid, const QString& processName);
|
ProcessMemoryProvider(uint32_t pid, const QString& processName);
|
||||||
~ProcessMemoryProvider() override;
|
~ProcessMemoryProvider() override;
|
||||||
|
|
||||||
// Required overrides
|
// Required overrides
|
||||||
bool read(uint64_t addr, void* buf, int len) const override;
|
bool read(uint64_t addr, void* buf, int len) const override;
|
||||||
int size() const override { return m_handle ? INT_MAX : NULL; } // Process memory has no fixed size
|
int size() const override;
|
||||||
|
|
||||||
// Optional overrides
|
// Optional overrides
|
||||||
bool write(uint64_t addr, const void* buf, int len) override;
|
bool write(uint64_t addr, const void* buf, int len) override;
|
||||||
bool isWritable() const override { return m_writable; }
|
bool isWritable() const override { return m_writable; }
|
||||||
QString name() const override { return m_processName; }
|
QString name() const override { return m_processName; }
|
||||||
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
||||||
QString getSymbol(uint64_t addr) const override;
|
QString getSymbol(uint64_t addr) const override;
|
||||||
|
|
||||||
// Process-specific helpers
|
// Process-specific helpers
|
||||||
DWORD pid() const { return m_pid; }
|
uint32_t pid() const { return m_pid; }
|
||||||
uint64_t baseAddress() const { return m_base; }
|
uint64_t baseAddress() const { return m_base; }
|
||||||
void refreshModules() { m_modules.clear(); cacheModules(); }
|
void refreshModules() { m_modules.clear(); cacheModules(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cacheModules();
|
void cacheModules();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HANDLE m_handle;
|
#ifdef _WIN32
|
||||||
DWORD m_pid;
|
void* m_handle;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
int m_fd;
|
||||||
|
#endif
|
||||||
|
uint32_t m_pid;
|
||||||
QString m_processName;
|
QString m_processName;
|
||||||
bool m_writable;
|
bool m_writable;
|
||||||
uint64_t m_base;
|
uint64_t m_base;
|
||||||
@@ -52,24 +55,25 @@ private:
|
|||||||
/**
|
/**
|
||||||
* Plugin that provides ProcessMemoryProvider
|
* Plugin that provides ProcessMemoryProvider
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryPlugin : public IProviderPlugin {
|
class ProcessMemoryPlugin : public IProviderPlugin
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
std::string Name() const override { return "Process Memory"; }
|
std::string Name() const override { return "Process Memory"; }
|
||||||
std::string Version() const override { return "1.0.0"; }
|
std::string Version() const override { return "1.0.0"; }
|
||||||
std::string Author() const override { return "ReclassX"; }
|
std::string Author() const override { return "ReclassX"; }
|
||||||
std::string Description() const override { return "Read and write memory from local running Windows processes"; }
|
std::string Description() const override { return "Read and write memory from local running processes"; }
|
||||||
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||||
QIcon Icon() const override;
|
QIcon Icon() const override;
|
||||||
|
|
||||||
bool canHandle(const QString& target) const override;
|
bool canHandle(const QString& target) const override;
|
||||||
std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg) override;
|
std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg) override;
|
||||||
uint64_t getInitialBaseAddress(const QString& target) const override;
|
uint64_t getInitialBaseAddress(const QString& target) const override;
|
||||||
bool selectTarget(QWidget* parent, QString* target) override;
|
bool selectTarget(QWidget* parent, QString* target) override;
|
||||||
|
|
||||||
// Optional: provide custom process list
|
// Optional: provide custom process list
|
||||||
bool providesProcessList() const override { return true; }
|
bool providesProcessList() const override { return true; }
|
||||||
QVector<PluginProcessInfo> enumerateProcesses() override;
|
QVector<PluginProcessInfo> enumerateProcesses() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Plugin export
|
// Plugin export
|
||||||
extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||||
|
|||||||
@@ -4,14 +4,20 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define RCX_PLUGIN_EXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define RCX_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
namespace rcx { class Provider; }
|
namespace rcx { class Provider; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin interface for ReclassX
|
* Plugin interface for ReclassX
|
||||||
*
|
*
|
||||||
* Plugins are loaded from the "Plugins" folder as DLLs.
|
* Plugins are loaded from the "Plugins" folder as shared libraries.
|
||||||
* Each plugin must export a C function: extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
* Each plugin must export a C function: extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||||
*/
|
*/
|
||||||
class IPlugin {
|
class IPlugin {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1376,7 +1376,7 @@ int main(int argc, char* argv[]) {
|
|||||||
QTimer::singleShot(1000, [&window, out]() {
|
QTimer::singleShot(1000, [&window, out]() {
|
||||||
QDir().mkpath(QFileInfo(out).absolutePath());
|
QDir().mkpath(QFileInfo(out).absolutePath());
|
||||||
window.grab().save(out);
|
window.grab().save(out);
|
||||||
::_exit(0); // immediate exit — no need for clean shutdown in screenshot mode
|
::_Exit(0); // immediate exit — no need for clean shutdown in screenshot mode
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
#include <tlhelp32.h>
|
#include <tlhelp32.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ProcessPicker::ProcessPicker(QWidget *parent)
|
ProcessPicker::ProcessPicker(QWidget *parent)
|
||||||
@@ -155,6 +160,45 @@ void ProcessPicker::enumerateProcesses()
|
|||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
QDir procDir("/proc");
|
||||||
|
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
|
|
||||||
|
for (const QString& entry : entries) {
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = entry.toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) continue;
|
||||||
|
|
||||||
|
// Read process name from /proc/<pid>/comm
|
||||||
|
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||||
|
QFile commFile(commPath);
|
||||||
|
QString procName;
|
||||||
|
if (commFile.open(QIODevice::ReadOnly)) {
|
||||||
|
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||||
|
commFile.close();
|
||||||
|
}
|
||||||
|
if (procName.isEmpty()) continue;
|
||||||
|
|
||||||
|
// Read exe path from /proc/<pid>/exe symlink
|
||||||
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||||
|
QFileInfo exeInfo(exePath);
|
||||||
|
QString resolvedPath;
|
||||||
|
if (exeInfo.exists())
|
||||||
|
resolvedPath = exeInfo.symLinkTarget();
|
||||||
|
|
||||||
|
// Skip if we can't read the process memory
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ProcessInfo info;
|
||||||
|
info.pid = pid;
|
||||||
|
info.name = procName;
|
||||||
|
info.path = resolvedPath;
|
||||||
|
info.icon = defaultIcon;
|
||||||
|
processes.append(info);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
// Platform not supported
|
// Platform not supported
|
||||||
QMessageBox::warning(this, "Error", "Process enumeration not supported on this platform.");
|
QMessageBox::warning(this, "Error", "Process enumeration not supported on this platform.");
|
||||||
|
|||||||
Reference in New Issue
Block a user