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,6 +26,16 @@ 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)
|
||||||
@@ -31,16 +54,8 @@ 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
|
||||||
{
|
{
|
||||||
@@ -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,9 +325,10 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
|||||||
// Parse target: "pid:name" or just "pid"
|
// Parse target: "pid:name" or just "pid"
|
||||||
QStringList parts = target.split(':');
|
QStringList parts = target.split(':');
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
DWORD pid = parts[0].toUInt(&ok);
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
|
||||||
if (!ok || pid == 0) {
|
if (!ok || pid == 0)
|
||||||
|
{
|
||||||
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -132,11 +339,9 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
|||||||
if (!provider->isValid())
|
if (!provider->isValid())
|
||||||
{
|
{
|
||||||
if (errorMsg)
|
if (errorMsg)
|
||||||
{
|
|
||||||
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
||||||
"Ensure the process is running and you have sufficient permissions.")
|
"Ensure the process is running and you have sufficient permissions.")
|
||||||
.arg(name).arg(pid);
|
.arg(name).arg(pid);
|
||||||
}
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,13 +369,36 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
|||||||
{
|
{
|
||||||
MODULEINFO mi{};
|
MODULEINFO mi{};
|
||||||
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
||||||
{
|
|
||||||
base = (uint64_t)mi.lpBaseOfDll;
|
base = (uint64_t)mi.lpBaseOfDll;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CloseHandle(hProc);
|
CloseHandle(hProc);
|
||||||
return base;
|
return base;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
// Parse PID from target
|
||||||
|
QStringList parts = target.split(':');
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = parts[0].toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) return 0;
|
||||||
|
|
||||||
|
// Find first executable mapping from /proc/<pid>/maps
|
||||||
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(pid);
|
||||||
|
std::ifstream mapsFile(mapsPath.toStdString());
|
||||||
|
if (!mapsFile.is_open()) return 0;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(mapsFile, line)) {
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string addrRange, perms;
|
||||||
|
iss >> addrRange >> perms;
|
||||||
|
if (perms.size() >= 3 && perms[2] == 'x') {
|
||||||
|
auto dash = addrRange.find('-');
|
||||||
|
if (dash != std::string::npos) {
|
||||||
|
return std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
#else
|
#else
|
||||||
Q_UNUSED(target);
|
Q_UNUSED(target);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -257,6 +485,45 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
QDir procDir("/proc");
|
||||||
|
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
|
|
||||||
|
for (const QString& entry : entries) {
|
||||||
|
bool ok = false;
|
||||||
|
uint32_t pid = entry.toUInt(&ok);
|
||||||
|
if (!ok || pid == 0) continue;
|
||||||
|
|
||||||
|
// Read process name from /proc/<pid>/comm
|
||||||
|
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||||
|
QFile commFile(commPath);
|
||||||
|
QString procName;
|
||||||
|
if (commFile.open(QIODevice::ReadOnly)) {
|
||||||
|
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||||
|
commFile.close();
|
||||||
|
}
|
||||||
|
if (procName.isEmpty()) continue; // Skip kernel threads with no name
|
||||||
|
|
||||||
|
// Read exe path from /proc/<pid>/exe symlink
|
||||||
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||||
|
QFileInfo exeInfo(exePath);
|
||||||
|
QString resolvedPath;
|
||||||
|
if (exeInfo.exists())
|
||||||
|
resolvedPath = exeInfo.symLinkTarget();
|
||||||
|
|
||||||
|
// Skip if we can't read the process memory (no access)
|
||||||
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||||
|
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PluginProcessInfo info;
|
||||||
|
info.pid = pid;
|
||||||
|
info.name = procName;
|
||||||
|
info.path = resolvedPath;
|
||||||
|
info.icon = defaultIcon;
|
||||||
|
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,23 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../../src/iplugin.h"
|
#include "../../src/iplugin.h"
|
||||||
#include "../../src/core.h"
|
#include "../../src/core.h"
|
||||||
#include <windows.h>
|
|
||||||
#include <tlhelp32.h>
|
#include <cstdint>
|
||||||
#include <psapi.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Windows process memory provider
|
* Process memory provider
|
||||||
* Reads/writes memory from a live process using Win32 API
|
* Reads/writes memory from a live process using platform APIs
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryProvider : public rcx::Provider {
|
class ProcessMemoryProvider : public rcx::Provider
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ProcessMemoryProvider(DWORD pid, const QString& processName);
|
ProcessMemoryProvider(uint32_t pid, const QString& processName);
|
||||||
~ProcessMemoryProvider() override;
|
~ProcessMemoryProvider() override;
|
||||||
|
|
||||||
// Required overrides
|
// Required overrides
|
||||||
bool read(uint64_t addr, void* buf, int len) const override;
|
bool read(uint64_t addr, void* buf, int len) const override;
|
||||||
int size() const override { return m_handle ? INT_MAX : NULL; } // Process memory has no fixed size
|
int size() const override;
|
||||||
|
|
||||||
// Optional overrides
|
// Optional overrides
|
||||||
bool write(uint64_t addr, const void* buf, int len) override;
|
bool write(uint64_t addr, const void* buf, int len) override;
|
||||||
@@ -27,7 +26,7 @@ public:
|
|||||||
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(); }
|
||||||
|
|
||||||
@@ -35,8 +34,12 @@ 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,12 +55,13 @@ private:
|
|||||||
/**
|
/**
|
||||||
* Plugin that provides ProcessMemoryProvider
|
* Plugin that provides ProcessMemoryProvider
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryPlugin : public IProviderPlugin {
|
class ProcessMemoryPlugin : public IProviderPlugin
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
std::string Name() const override { return "Process Memory"; }
|
std::string Name() const override { return "Process Memory"; }
|
||||||
std::string Version() const override { return "1.0.0"; }
|
std::string Version() const override { return "1.0.0"; }
|
||||||
std::string Author() const override { return "ReclassX"; }
|
std::string Author() const override { return "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;
|
||||||
|
|
||||||
@@ -72,4 +76,4 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 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