From 4029b05298900017f22f066d553a81ad0cab5a39 Mon Sep 17 00:00:00 2001 From: Sen66 Date: Mon, 9 Feb 2026 15:09:42 +0100 Subject: [PATCH 1/2] Added linux support (tested on Ubuntu) CMakeList: fixed for building on linux processpicker: linux process enumeration main.cpp: "_Exit()" works on linux & windows "ProcessMemory" plugin: added linux support --- CMakeLists.txt | 25 +- plugins/ProcessMemory/CMakeLists.txt | 12 +- plugins/ProcessMemory/ProcessMemoryPlugin.cpp | 357 +++++++++++++++--- plugins/ProcessMemory/ProcessMemoryPlugin.h | 46 +-- src/iplugin.h | 12 +- src/main.cpp | 2 +- src/processpicker.cpp | 44 +++ 7 files changed, 421 insertions(+), 77 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a74ff00..44e5fa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,9 +43,10 @@ target_link_libraries(ReclassX PRIVATE Qt6::Svg Qt6::Concurrent QScintilla::QScintilla - dbghelp - psapi ) +if(WIN32) + target_link_libraries(ReclassX PRIVATE dbghelp psapi) +endif() add_custom_target(screenshot ALL COMMAND ReclassX --screenshot ${CMAKE_BINARY_DIR}/screenshot.png @@ -140,7 +141,10 @@ if(BUILD_TESTING) target_include_directories(test_controller PRIVATE src) target_link_libraries(test_controller PRIVATE Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test - QScintilla::QScintilla dbghelp psapi) + QScintilla::QScintilla) + if(WIN32) + target_link_libraries(test_controller PRIVATE dbghelp psapi) + endif() add_test(NAME test_controller COMMAND test_controller) add_executable(test_validation tests/test_validation.cpp @@ -149,7 +153,10 @@ if(BUILD_TESTING) target_include_directories(test_validation PRIVATE src) target_link_libraries(test_validation PRIVATE Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test - QScintilla::QScintilla dbghelp psapi) + QScintilla::QScintilla) + if(WIN32) + target_link_libraries(test_validation PRIVATE dbghelp psapi) + endif() add_test(NAME test_validation COMMAND test_validation) add_executable(test_generator tests/test_generator.cpp @@ -164,7 +171,10 @@ if(BUILD_TESTING) target_include_directories(test_context_menu PRIVATE src) target_link_libraries(test_context_menu PRIVATE 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_executable(test_new_features tests/test_new_features.cpp @@ -173,7 +183,10 @@ if(BUILD_TESTING) target_include_directories(test_new_features PRIVATE src) target_link_libraries(test_new_features PRIVATE 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) endif() add_subdirectory(plugins/ProcessMemory) diff --git a/plugins/ProcessMemory/CMakeLists.txt b/plugins/ProcessMemory/CMakeLists.txt index b114ee8..31d0400 100644 --- a/plugins/ProcessMemory/CMakeLists.txt +++ b/plugins/ProcessMemory/CMakeLists.txt @@ -26,8 +26,18 @@ add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES}) # Link Qt 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 -target_include_directories(ProcessMemoryPlugin PRIVATE +target_include_directories(ProcessMemoryPlugin PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../src ) diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp index 1f58a0f..2f04b4e 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp @@ -1,17 +1,40 @@ #include "ProcessMemoryPlugin.h" + #include "../../src/processpicker.h" + #include #include #include #include #include #include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#elif defined(__linux__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // ────────────────────────────────────────────────────────────────────────── // ProcessMemoryProvider implementation // ────────────────────────────────────────────────────────────────────────── -ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processName) +#ifdef _WIN32 + +ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName) : m_handle(nullptr) , m_pid(pid) , m_processName(processName) @@ -19,7 +42,7 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa , m_base(0) { // 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); if (m_handle) m_writable = true; @@ -31,21 +54,13 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa } if (m_handle) - { cacheModules(); - } -} - -ProcessMemoryProvider::~ProcessMemoryProvider() -{ - if (m_handle) - CloseHandle(m_handle); } bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const { if (!m_handle || len <= 0) return false; - + SIZE_T bytesRead = 0; if (ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead)) 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) { 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)) 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 { - // TODO: Implement module enumeration with EnumProcessModules - // For now, just return empty (no symbol resolution) - Q_UNUSED(addr); + 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 {}; } @@ -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(len); + + struct iovec remote; + remote.iov_base = reinterpret_cast(absAddr); + remote.iov_len = static_cast(len); + + ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0); + if (nread == static_cast(len)) + return true; + + // Fallback: pread on /proc//mem + nread = ::pread(m_fd, buf, static_cast(len), static_cast(absAddr)); + return nread == static_cast(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(buf); + local.iov_len = static_cast(len); + + struct iovec remote; + remote.iov_base = reinterpret_cast(absAddr); + remote.iov_len = static_cast(len); + + ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0); + if (nwritten == static_cast(len)) + return true; + + // Fallback: pwrite on /proc//mem + nwritten = ::pwrite(m_fd, buf, static_cast(len), static_cast(absAddr)); + return nwritten == static_cast(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//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 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 // ────────────────────────────────────────────────────────────────────────── @@ -119,27 +325,26 @@ std::unique_ptr ProcessMemoryPlugin::createProvider(const QString // Parse target: "pid:name" or just "pid" QStringList parts = target.split(':'); bool ok = false; - DWORD pid = parts[0].toUInt(&ok); - - if (!ok || pid == 0) { + uint32_t pid = parts[0].toUInt(&ok); + + if (!ok || pid == 0) + { if (errorMsg) *errorMsg = "Invalid PID: " + target; return nullptr; } - + QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid); - + auto provider = std::make_unique(pid, name); if (!provider->isValid()) { if (errorMsg) - { *errorMsg = QString("Failed to open process %1 (PID: %2)\n" "Ensure the process is running and you have sufficient permissions.") .arg(name).arg(pid); - } return nullptr; } - + return provider; } @@ -151,26 +356,49 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const bool ok = false; DWORD pid = parts[0].toUInt(&ok); if (!ok || pid == 0) return 0; - + // Open process to get main module base HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); if (!hProc) return 0; - + uint64_t base = 0; HMODULE hMod = nullptr; DWORD needed = 0; - + if (EnumProcessModulesEx(hProc, &hMod, sizeof(hMod), &needed, LIST_MODULES_ALL) && hMod) { MODULEINFO mi{}; if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi))) - { base = (uint64_t)mi.lpBaseOfDll; - } } - + CloseHandle(hProc); 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//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 Q_UNUSED(target); return 0; @@ -181,7 +409,7 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target) { // Use custom process enumeration from plugin QVector pluginProcesses = enumerateProcesses(); - + // Convert to ProcessInfo for ProcessPicker QList processes; for (const auto& pinfo : pluginProcesses) @@ -193,50 +421,50 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target) info.icon = pinfo.icon; processes.append(info); } - + // Show ProcessPicker with custom process list ProcessPicker picker(processes, parent); if (picker.exec() == QDialog::Accepted) { uint32_t pid = picker.selectedProcessId(); QString name = picker.selectedProcessName(); - + // Format target as "pid:name" *target = QString("%1:%2").arg(pid).arg(name); return true; } - + return false; } QVector ProcessMemoryPlugin::enumerateProcesses() { QVector 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); - + // Try to get full path and icon 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); - + // Try QueryFullProcessImageNameW first if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) { info.path = QString::fromWCharArray(path); - + // Extract icon SHFILEINFOW sfi = {}; if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) { @@ -247,18 +475,57 @@ QVector ProcessMemoryPlugin::enumerateProcesses() } } } - + CloseHandle(hProcess); } - + processes.append(info); - + } while (Process32NextW(snapshot, &entry)); } - + 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//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//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 - + return processes; } @@ -266,7 +533,7 @@ QVector ProcessMemoryPlugin::enumerateProcesses() // Plugin factory // ────────────────────────────────────────────────────────────────────────── -extern "C" __declspec(dllexport) IPlugin* CreatePlugin() +extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin() { return new ProcessMemoryPlugin(); } diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.h b/plugins/ProcessMemory/ProcessMemoryPlugin.h index 456ea7a..089d4fc 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.h +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.h @@ -1,42 +1,45 @@ #pragma once #include "../../src/iplugin.h" #include "../../src/core.h" -#include -#include -#include -#include + +#include /** - * Windows process memory provider - * Reads/writes memory from a live process using Win32 API + * Process memory provider + * Reads/writes memory from a live process using platform APIs */ -class ProcessMemoryProvider : public rcx::Provider { +class ProcessMemoryProvider : public rcx::Provider +{ public: - ProcessMemoryProvider(DWORD pid, const QString& processName); + ProcessMemoryProvider(uint32_t pid, const QString& processName); ~ProcessMemoryProvider() override; - + // Required overrides 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 bool write(uint64_t addr, const void* buf, int len) override; bool isWritable() const override { return m_writable; } QString name() const override { return m_processName; } QString kind() const override { return QStringLiteral("LocalProcess"); } QString getSymbol(uint64_t addr) const override; - + // Process-specific helpers - DWORD pid() const { return m_pid; } + uint32_t pid() const { return m_pid; } uint64_t baseAddress() const { return m_base; } void refreshModules() { m_modules.clear(); cacheModules(); } private: void cacheModules(); - + private: - HANDLE m_handle; - DWORD m_pid; +#ifdef _WIN32 + void* m_handle; +#elif defined(__linux__) + int m_fd; +#endif + uint32_t m_pid; QString m_processName; bool m_writable; uint64_t m_base; @@ -52,24 +55,25 @@ private: /** * Plugin that provides ProcessMemoryProvider */ -class ProcessMemoryPlugin : public IProviderPlugin { +class ProcessMemoryPlugin : public IProviderPlugin +{ public: std::string Name() const override { return "Process Memory"; } std::string Version() const override { return "1.0.0"; } 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; } QIcon Icon() const override; - + bool canHandle(const QString& target) const override; std::unique_ptr createProvider(const QString& target, QString* errorMsg) override; uint64_t getInitialBaseAddress(const QString& target) const override; bool selectTarget(QWidget* parent, QString* target) override; - + // Optional: provide custom process list bool providesProcessList() const override { return true; } QVector enumerateProcesses() override; }; // Plugin export -extern "C" __declspec(dllexport) IPlugin* CreatePlugin(); +extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin(); diff --git a/src/iplugin.h b/src/iplugin.h index 2e2ed9f..fca3b20 100644 --- a/src/iplugin.h +++ b/src/iplugin.h @@ -4,14 +4,20 @@ #include #include +#ifdef _WIN32 + #define RCX_PLUGIN_EXPORT __declspec(dllexport) +#else + #define RCX_PLUGIN_EXPORT __attribute__((visibility("default"))) +#endif + // Forward declaration namespace rcx { class Provider; } /** * Plugin interface for ReclassX - * - * Plugins are loaded from the "Plugins" folder as DLLs. - * Each plugin must export a C function: extern "C" __declspec(dllexport) IPlugin* CreatePlugin(); + * + * Plugins are loaded from the "Plugins" folder as shared libraries. + * Each plugin must export a C function: extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin(); */ class IPlugin { public: diff --git a/src/main.cpp b/src/main.cpp index 76f8835..289334b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1188,7 +1188,7 @@ int main(int argc, char* argv[]) { QTimer::singleShot(1000, [&window, out]() { QDir().mkpath(QFileInfo(out).absolutePath()); 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 }); } diff --git a/src/processpicker.cpp b/src/processpicker.cpp index 54704fd..bdd3b94 100644 --- a/src/processpicker.cpp +++ b/src/processpicker.cpp @@ -11,6 +11,11 @@ #include #include #include +#elif defined(__linux__) +#include +#include +#include +#include #endif ProcessPicker::ProcessPicker(QWidget *parent) @@ -155,6 +160,45 @@ void ProcessPicker::enumerateProcesses() } 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//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//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 // Platform not supported QMessageBox::warning(this, "Error", "Process enumeration not supported on this platform."); From b6c713eb29407d89dba4dcd69f1f99e7ea062980 Mon Sep 17 00:00:00 2001 From: Sen66 Date: Mon, 9 Feb 2026 15:14:35 +0100 Subject: [PATCH 2/2] Updated Readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90cde4f..982b6d7 100644 --- a/README.md +++ b/README.md @@ -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 ![screenshot](screenshot.png) 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 @@ -22,7 +22,7 @@ We hit some big milestones (first plugin+first linux build) however project is s cd ReclassX .\scripts\build_qscintilla.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