mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Merge branch 'plugin-system' into main
Adds basic plugin system support with plugin manager, provider registry, plugin interface, and ProcessMemory example plugin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,10 @@ add_executable(ReclassX
|
|||||||
src/core.h
|
src/core.h
|
||||||
src/workspace_model.h
|
src/workspace_model.h
|
||||||
src/providers/buffer_provider.h src/providers/null_provider.h src/providers/process_provider.h src/providers/provider.h src/providers/snapshot_provider.h
|
src/providers/buffer_provider.h src/providers/null_provider.h src/providers/process_provider.h src/providers/provider.h src/providers/snapshot_provider.h
|
||||||
|
src/providerregistry.cpp
|
||||||
|
src/providerregistry.h
|
||||||
|
src/pluginmanager.cpp
|
||||||
|
src/pluginmanager.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(ReclassX PRIVATE src)
|
target_include_directories(ReclassX PRIVATE src)
|
||||||
@@ -105,7 +109,7 @@ if(BUILD_TESTING)
|
|||||||
target_link_libraries(test_compose PRIVATE Qt6::Core Qt6::Test)
|
target_link_libraries(test_compose PRIVATE Qt6::Core Qt6::Test)
|
||||||
add_test(NAME test_compose COMMAND test_compose)
|
add_test(NAME test_compose COMMAND test_compose)
|
||||||
|
|
||||||
add_executable(test_editor tests/test_editor.cpp src/editor.cpp src/compose.cpp src/format.cpp)
|
add_executable(test_editor tests/test_editor.cpp src/editor.cpp src/compose.cpp src/format.cpp src/providerregistry.cpp)
|
||||||
target_include_directories(test_editor PRIVATE src)
|
target_include_directories(test_editor PRIVATE src)
|
||||||
target_link_libraries(test_editor PRIVATE
|
target_link_libraries(test_editor PRIVATE
|
||||||
Qt6::Widgets Qt6::PrintSupport Qt6::Test
|
Qt6::Widgets Qt6::PrintSupport Qt6::Test
|
||||||
@@ -132,7 +136,7 @@ if(BUILD_TESTING)
|
|||||||
|
|
||||||
add_executable(test_controller tests/test_controller.cpp
|
add_executable(test_controller tests/test_controller.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui)
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
||||||
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
|
||||||
@@ -141,7 +145,7 @@ if(BUILD_TESTING)
|
|||||||
|
|
||||||
add_executable(test_validation tests/test_validation.cpp
|
add_executable(test_validation tests/test_validation.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui)
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
||||||
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
|
||||||
@@ -156,7 +160,7 @@ if(BUILD_TESTING)
|
|||||||
|
|
||||||
add_executable(test_context_menu tests/test_context_menu.cpp
|
add_executable(test_context_menu tests/test_context_menu.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui)
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
||||||
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
|
||||||
@@ -165,10 +169,11 @@ if(BUILD_TESTING)
|
|||||||
|
|
||||||
add_executable(test_new_features tests/test_new_features.cpp
|
add_executable(test_new_features tests/test_new_features.cpp
|
||||||
src/generator.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
src/generator.cpp src/compose.cpp src/format.cpp src/controller.cpp
|
||||||
src/editor.cpp src/processpicker.cpp src/processpicker.ui)
|
src/editor.cpp src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp)
|
||||||
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 dbghelp psapi)
|
||||||
add_test(NAME test_new_features COMMAND test_new_features)
|
add_test(NAME test_new_features COMMAND test_new_features)
|
||||||
endif()
|
endif()
|
||||||
|
add_subdirectory(plugins/ProcessMemory)
|
||||||
|
|||||||
38
plugins/ProcessMemory/CMakeLists.txt
Normal file
38
plugins/ProcessMemory/CMakeLists.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(ProcessMemoryPlugin LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Find Qt
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
|
# Plugin sources
|
||||||
|
set(PLUGIN_SOURCES
|
||||||
|
ProcessMemoryPlugin.h
|
||||||
|
ProcessMemoryPlugin.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create shared library (DLL)
|
||||||
|
add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||||
|
|
||||||
|
# Link Qt
|
||||||
|
target_link_libraries(ProcessMemoryPlugin PRIVATE Qt6::Widgets)
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
target_include_directories(ProcessMemoryPlugin PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output to Plugins folder
|
||||||
|
set_target_properties(ProcessMemoryPlugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
|
)
|
||||||
272
plugins/ProcessMemory/ProcessMemoryPlugin.cpp
Normal file
272
plugins/ProcessMemory/ProcessMemoryPlugin.cpp
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#include "ProcessMemoryPlugin.h"
|
||||||
|
#include "../../src/processpicker.h"
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QImage>
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
// ProcessMemoryProvider implementation
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processName)
|
||||||
|
: m_handle(nullptr)
|
||||||
|
, m_pid(pid)
|
||||||
|
, m_processName(processName)
|
||||||
|
, m_writable(false)
|
||||||
|
, 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,
|
||||||
|
FALSE, pid);
|
||||||
|
if (m_handle)
|
||||||
|
m_writable = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fall back to read-only
|
||||||
|
m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid);
|
||||||
|
m_writable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
|
{
|
||||||
|
// TODO: Implement module enumeration with EnumProcessModules
|
||||||
|
// For now, just return empty (no symbol resolution)
|
||||||
|
Q_UNUSED(addr);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessMemoryProvider::cacheModules()
|
||||||
|
{
|
||||||
|
HMODULE mods[1024];
|
||||||
|
DWORD needed = 0;
|
||||||
|
if (!EnumProcessModulesEx(m_handle, mods, sizeof(mods),
|
||||||
|
&needed, LIST_MODULES_ALL))
|
||||||
|
return;
|
||||||
|
int count = qMin((int)(needed / sizeof(HMODULE)), 1024);
|
||||||
|
m_modules.reserve(count);
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
MODULEINFO mi{};
|
||||||
|
WCHAR modName[MAX_PATH];
|
||||||
|
if (GetModuleInformation(m_handle, mods[i], &mi, sizeof(mi))
|
||||||
|
&& GetModuleBaseNameW(m_handle, mods[i], modName, MAX_PATH))
|
||||||
|
{
|
||||||
|
if ( i == 0 )
|
||||||
|
m_base = (uint64_t)mi.lpBaseOfDll;
|
||||||
|
|
||||||
|
m_modules.append({
|
||||||
|
QString::fromWCharArray(modName),
|
||||||
|
(uint64_t)mi.lpBaseOfDll,
|
||||||
|
(uint64_t)mi.SizeOfImage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
// ProcessMemoryPlugin implementation
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
QIcon ProcessMemoryPlugin::Icon() const
|
||||||
|
{
|
||||||
|
return qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessMemoryPlugin::canHandle(const QString& target) const
|
||||||
|
{
|
||||||
|
// Target format: "pid:name" or just "pid"
|
||||||
|
QRegularExpression re("^\\d+");
|
||||||
|
return re.match(target).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
|
||||||
|
{
|
||||||
|
// Parse target: "pid:name" or just "pid"
|
||||||
|
QStringList parts = target.split(':');
|
||||||
|
bool ok = false;
|
||||||
|
DWORD 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<ProcessMemoryProvider>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Parse PID from target
|
||||||
|
QStringList parts = target.split(':');
|
||||||
|
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;
|
||||||
|
#else
|
||||||
|
Q_UNUSED(target);
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||||
|
{
|
||||||
|
// Use custom process enumeration from plugin
|
||||||
|
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
||||||
|
|
||||||
|
// Convert to ProcessInfo for ProcessPicker
|
||||||
|
QList<ProcessInfo> processes;
|
||||||
|
for (const auto& pinfo : pluginProcesses)
|
||||||
|
{
|
||||||
|
ProcessInfo info;
|
||||||
|
info.pid = pinfo.pid;
|
||||||
|
info.name = pinfo.name;
|
||||||
|
info.path = pinfo.path;
|
||||||
|
info.icon = pinfo.icon;
|
||||||
|
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<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||||
|
{
|
||||||
|
QVector<PluginProcessInfo> processes;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if (snapshot == INVALID_HANDLE_VALUE) {
|
||||||
|
return processes;
|
||||||
|
}
|
||||||
|
|
||||||
|
PROCESSENTRY32W entry;
|
||||||
|
entry.dwSize = sizeof(entry);
|
||||||
|
|
||||||
|
if (Process32FirstW(snapshot, &entry)) {
|
||||||
|
do {
|
||||||
|
PluginProcessInfo info;
|
||||||
|
info.pid = entry.th32ProcessID;
|
||||||
|
info.name = QString::fromWCharArray(entry.szExeFile);
|
||||||
|
|
||||||
|
// 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)) {
|
||||||
|
if (sfi.hIcon) {
|
||||||
|
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
||||||
|
info.icon = QIcon(pixmap);
|
||||||
|
DestroyIcon(sfi.hIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
processes.append(info);
|
||||||
|
|
||||||
|
} while (Process32NextW(snapshot, &entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(snapshot);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return processes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
// Plugin factory
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
extern "C" __declspec(dllexport) IPlugin* CreatePlugin()
|
||||||
|
{
|
||||||
|
return new ProcessMemoryPlugin();
|
||||||
|
}
|
||||||
75
plugins/ProcessMemory/ProcessMemoryPlugin.h
Normal file
75
plugins/ProcessMemory/ProcessMemoryPlugin.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../../src/iplugin.h"
|
||||||
|
#include "../../src/core.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows process memory provider
|
||||||
|
* Reads/writes memory from a live process using Win32 API
|
||||||
|
*/
|
||||||
|
class ProcessMemoryProvider : public rcx::Provider {
|
||||||
|
public:
|
||||||
|
ProcessMemoryProvider(DWORD 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
|
||||||
|
|
||||||
|
// 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; }
|
||||||
|
uint64_t baseAddress() const { return m_base; }
|
||||||
|
void refreshModules() { m_modules.clear(); cacheModules(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cacheModules();
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE m_handle;
|
||||||
|
DWORD m_pid;
|
||||||
|
QString m_processName;
|
||||||
|
bool m_writable;
|
||||||
|
uint64_t m_base;
|
||||||
|
|
||||||
|
struct ModuleInfo {
|
||||||
|
QString name;
|
||||||
|
uint64_t base;
|
||||||
|
uint64_t size;
|
||||||
|
};
|
||||||
|
QVector<ModuleInfo> m_modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin that provides ProcessMemoryProvider
|
||||||
|
*/
|
||||||
|
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"; }
|
||||||
|
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||||
|
QIcon Icon() const override;
|
||||||
|
|
||||||
|
bool canHandle(const QString& target) const override;
|
||||||
|
std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg) override;
|
||||||
|
uint64_t getInitialBaseAddress(const QString& target) const override;
|
||||||
|
bool selectTarget(QWidget* parent, QString* target) override;
|
||||||
|
|
||||||
|
// Optional: provide custom process list
|
||||||
|
bool providesProcessList() const override { return true; }
|
||||||
|
QVector<PluginProcessInfo> enumerateProcesses() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plugin export
|
||||||
|
extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "providers/process_provider.h"
|
#include "providers/process_provider.h"
|
||||||
|
#include "providerregistry.h"
|
||||||
#include "processpicker.h"
|
#include "processpicker.h"
|
||||||
#include <Qsci/qsciscintilla.h>
|
#include <Qsci/qsciscintilla.h>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
@@ -405,6 +406,51 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Look up provider in registry
|
||||||
|
const auto* providerInfo = ProviderRegistry::instance().findProvider(text.toLower().replace(" ", ""));
|
||||||
|
|
||||||
|
if (providerInfo) {
|
||||||
|
QString target;
|
||||||
|
bool selected = false;
|
||||||
|
|
||||||
|
// Execute provider's target selection
|
||||||
|
if (providerInfo->isBuiltin) {
|
||||||
|
// Built-in provider with factory function
|
||||||
|
if (providerInfo->factory) {
|
||||||
|
selected = providerInfo->factory(qobject_cast<QWidget*>(parent()), &target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Plugin-based provider
|
||||||
|
if (providerInfo->plugin) {
|
||||||
|
selected = providerInfo->plugin->selectTarget(qobject_cast<QWidget*>(parent()), &target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected && !target.isEmpty()) {
|
||||||
|
// Create provider from target
|
||||||
|
std::unique_ptr<Provider> provider;
|
||||||
|
QString errorMsg;
|
||||||
|
|
||||||
|
if (providerInfo->plugin)
|
||||||
|
{
|
||||||
|
provider = providerInfo->plugin->createProvider(target, &errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply provider or show error
|
||||||
|
if (provider) {
|
||||||
|
m_doc->undoStack.clear();
|
||||||
|
m_doc->provider = std::move(provider);
|
||||||
|
m_doc->dataPath.clear();
|
||||||
|
emit m_doc->documentChanged();
|
||||||
|
refresh();
|
||||||
|
} else if (!errorMsg.isEmpty()) {
|
||||||
|
QMessageBox::warning(qobject_cast<QWidget*>(parent()), "Provider Error", errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EditTarget::ArrayElementType: {
|
case EditTarget::ArrayElementType: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "editor.h"
|
#include "editor.h"
|
||||||
|
#include "providerregistry.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <Qsci/qsciscintilla.h>
|
#include <Qsci/qsciscintilla.h>
|
||||||
#include <Qsci/qsciscintillabase.h>
|
#include <Qsci/qsciscintillabase.h>
|
||||||
@@ -1665,6 +1666,11 @@ void RcxEditor::showSourcePicker() {
|
|||||||
menu.addAction("file");
|
menu.addAction("file");
|
||||||
menu.addAction("process");
|
menu.addAction("process");
|
||||||
|
|
||||||
|
// Add all registered providers from global registry
|
||||||
|
const auto& providers = ProviderRegistry::instance().providers();
|
||||||
|
for (const auto& provider : providers)
|
||||||
|
menu.addAction(provider.name);
|
||||||
|
|
||||||
// Saved sources below separator (with checkmarks)
|
// Saved sources below separator (with checkmarks)
|
||||||
if (!m_savedSourceDisplay.isEmpty()) {
|
if (!m_savedSourceDisplay.isEmpty()) {
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|||||||
130
src/iplugin.h
Normal file
130
src/iplugin.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QString>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
*/
|
||||||
|
class IPlugin {
|
||||||
|
public:
|
||||||
|
virtual ~IPlugin() = default;
|
||||||
|
|
||||||
|
// Plugin metadata
|
||||||
|
virtual std::string Name() const = 0;
|
||||||
|
virtual std::string Version() const = 0;
|
||||||
|
virtual std::string Author() const = 0;
|
||||||
|
virtual std::string Description() const = 0;
|
||||||
|
virtual QIcon Icon() const { return QIcon(); }
|
||||||
|
|
||||||
|
// Plugin type - determines what functionality it provides
|
||||||
|
enum k_EType
|
||||||
|
{
|
||||||
|
// Provides memory/data sources
|
||||||
|
ProviderPlugin,
|
||||||
|
|
||||||
|
// In the future we could make plugins that change the main UI
|
||||||
|
// for loading different data sources
|
||||||
|
};
|
||||||
|
virtual k_EType Type() const = 0;
|
||||||
|
|
||||||
|
// Plugin load type - determines whether and when the plugin is loaded
|
||||||
|
// by the PluginManager
|
||||||
|
enum k_ELoadType
|
||||||
|
{
|
||||||
|
// Plugin is automatically loaded on startup
|
||||||
|
k_ELoadTypeAuto,
|
||||||
|
|
||||||
|
// Plugin must be loaded manually via 'Manage Plugins'
|
||||||
|
k_ELoadTypeManual,
|
||||||
|
};
|
||||||
|
virtual k_ELoadType LoadType() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class QWidget;
|
||||||
|
class QTableWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process information structure for custom process lists
|
||||||
|
*/
|
||||||
|
struct PluginProcessInfo {
|
||||||
|
uint32_t pid;
|
||||||
|
QString name;
|
||||||
|
QString path;
|
||||||
|
QIcon icon;
|
||||||
|
|
||||||
|
PluginProcessInfo() : pid(0) {}
|
||||||
|
PluginProcessInfo(uint32_t p, const QString& n, const QString& pth = QString(), const QIcon& i = QIcon())
|
||||||
|
: pid(p), name(n), path(pth), icon(i) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider plugin interface
|
||||||
|
*
|
||||||
|
* Plugins that implement this interface can create Provider instances
|
||||||
|
* for reading/writing memory from various sources (processes, files, network, etc.)
|
||||||
|
*/
|
||||||
|
class IProviderPlugin : public IPlugin {
|
||||||
|
public:
|
||||||
|
k_EType Type() const override { return ProviderPlugin; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this plugin can create a provider for the given target
|
||||||
|
* @param target - Target identifier (e.g., PID for process, path for file)
|
||||||
|
* @return true if this plugin can handle the target
|
||||||
|
*/
|
||||||
|
virtual bool canHandle(const QString& target) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a provider instance
|
||||||
|
* @param target - Target identifier
|
||||||
|
* @param errorMsg - Output parameter for error message if creation fails
|
||||||
|
* @return Provider instance, or nullptr on failure
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg = nullptr) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get initial base address for the provider (optional)
|
||||||
|
* Called after createProvider to set the document's base address
|
||||||
|
* @param target - Same target identifier passed to createProvider
|
||||||
|
* @return Initial base address, or 0 if not applicable
|
||||||
|
*/
|
||||||
|
virtual uint64_t getInitialBaseAddress(const QString& target) const { Q_UNUSED(target); return 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a dialog to select a target (e.g., process picker)
|
||||||
|
* @param parent - Parent widget for dialog
|
||||||
|
* @param target - Output parameter for selected target
|
||||||
|
* @return true if user selected a target, false if cancelled
|
||||||
|
*/
|
||||||
|
virtual bool selectTarget(QWidget* parent, QString* target) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom process list (optional)
|
||||||
|
*
|
||||||
|
* If implemented, this allows the plugin to override the default process enumeration.
|
||||||
|
* Return an empty list to use the default process picker.
|
||||||
|
*
|
||||||
|
* @return List of processes to display, or empty list to use default
|
||||||
|
*/
|
||||||
|
virtual QVector<PluginProcessInfo> enumerateProcesses() { return QVector<PluginProcessInfo>(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this plugin wants to override the process list
|
||||||
|
* @return true if enumerateProcesses() should be called
|
||||||
|
*/
|
||||||
|
virtual bool providesProcessList() const { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plugin factory function signature
|
||||||
|
typedef IPlugin* (*CreatePluginFunc)();
|
||||||
|
|
||||||
|
#define IPLUGIN_IID "com.reclassx.IPlugin/1.0"
|
||||||
112
src/main.cpp
112
src/main.cpp
@@ -1,5 +1,6 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "generator.h"
|
#include "generator.h"
|
||||||
|
#include "pluginmanager.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMdiArea>
|
#include <QMdiArea>
|
||||||
@@ -27,6 +28,8 @@
|
|||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QPushButton>
|
||||||
#include "workspace_model.h"
|
#include "workspace_model.h"
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
@@ -152,6 +155,7 @@ private:
|
|||||||
|
|
||||||
QMdiArea* m_mdiArea;
|
QMdiArea* m_mdiArea;
|
||||||
QLabel* m_statusLabel;
|
QLabel* m_statusLabel;
|
||||||
|
PluginManager m_pluginManager;
|
||||||
|
|
||||||
struct TabState {
|
struct TabState {
|
||||||
RcxDocument* doc;
|
RcxDocument* doc;
|
||||||
@@ -170,6 +174,7 @@ private:
|
|||||||
|
|
||||||
void createMenus();
|
void createMenus();
|
||||||
void createStatusBar();
|
void createStatusBar();
|
||||||
|
void showPluginsDialog();
|
||||||
QIcon makeIcon(const QString& svgPath);
|
QIcon makeIcon(const QString& svgPath);
|
||||||
|
|
||||||
RcxController* activeController() const;
|
RcxController* activeController() const;
|
||||||
@@ -205,6 +210,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
createMenus();
|
createMenus();
|
||||||
createStatusBar();
|
createStatusBar();
|
||||||
|
|
||||||
|
// Load plugins
|
||||||
|
m_pluginManager.LoadPlugins();
|
||||||
|
|
||||||
connect(m_mdiArea, &QMdiArea::subWindowActivated,
|
connect(m_mdiArea, &QMdiArea::subWindowActivated,
|
||||||
this, [this](QMdiSubWindow*) {
|
this, [this](QMdiSubWindow*) {
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
@@ -290,6 +298,10 @@ void MainWindow::createMenus() {
|
|||||||
node->addAction(makeIcon(":/vsicons/edit.svg"), "Re&name", QKeySequence(Qt::Key_F2), this, &MainWindow::renameNodeAction);
|
node->addAction(makeIcon(":/vsicons/edit.svg"), "Re&name", QKeySequence(Qt::Key_F2), this, &MainWindow::renameNodeAction);
|
||||||
node->addAction(makeIcon(":/vsicons/files.svg"), "D&uplicate", this, &MainWindow::duplicateNodeAction)->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
|
node->addAction(makeIcon(":/vsicons/files.svg"), "D&uplicate", this, &MainWindow::duplicateNodeAction)->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
auto* plugins = menuBar()->addMenu("&Plugins");
|
||||||
|
plugins->addAction("&Manage Plugins...", this, &MainWindow::showPluginsDialog);
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
auto* help = menuBar()->addMenu("&Help");
|
auto* help = menuBar()->addMenu("&Help");
|
||||||
help->addAction(makeIcon(":/vsicons/question.svg"), "&About ReclassX", this, &MainWindow::about);
|
help->addAction(makeIcon(":/vsicons/question.svg"), "&About ReclassX", this, &MainWindow::about);
|
||||||
@@ -1014,6 +1026,106 @@ void MainWindow::rebuildWorkspaceModel() {
|
|||||||
m_workspaceTree->expandAll();
|
m_workspaceTree->expandAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::showPluginsDialog() {
|
||||||
|
QDialog dialog(this);
|
||||||
|
dialog.setWindowTitle("Plugins");
|
||||||
|
dialog.resize(600, 400);
|
||||||
|
|
||||||
|
auto* layout = new QVBoxLayout(&dialog);
|
||||||
|
|
||||||
|
auto* list = new QListWidget();
|
||||||
|
layout->addWidget(list);
|
||||||
|
|
||||||
|
auto refreshList = [&]() {
|
||||||
|
list->clear();
|
||||||
|
|
||||||
|
// Populate plugin list
|
||||||
|
for (IPlugin* plugin : m_pluginManager.plugins()) {
|
||||||
|
QString typeStr;
|
||||||
|
switch (plugin->Type())
|
||||||
|
{
|
||||||
|
case IPlugin::ProviderPlugin: typeStr = "Provider"; break;
|
||||||
|
default: typeStr = "Unknown"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text = QString("%1 v%2\n %3\n Type: %4\n Author: %5")
|
||||||
|
.arg(QString::fromStdString(plugin->Name()))
|
||||||
|
.arg(QString::fromStdString(plugin->Version()))
|
||||||
|
.arg(QString::fromStdString(plugin->Description()))
|
||||||
|
.arg(typeStr)
|
||||||
|
.arg(QString::fromStdString(plugin->Author()));
|
||||||
|
|
||||||
|
auto* item = new QListWidgetItem(plugin->Icon(), text);
|
||||||
|
item->setData(Qt::UserRole, QString::fromStdString(plugin->Name()));
|
||||||
|
list->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pluginManager.plugins().isEmpty()) {
|
||||||
|
list->addItem("No plugins loaded");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshList();
|
||||||
|
|
||||||
|
// Button row
|
||||||
|
auto* btnLayout = new QHBoxLayout();
|
||||||
|
|
||||||
|
auto* btnLoad = new QPushButton("Load Plugin...");
|
||||||
|
connect(btnLoad, &QPushButton::clicked, [&, refreshList]() {
|
||||||
|
QString path = QFileDialog::getOpenFileName(&dialog, "Load Plugin",
|
||||||
|
QCoreApplication::applicationDirPath() + "/Plugins",
|
||||||
|
"Plugins (*.dll *.so *.dylib);;All Files (*)");
|
||||||
|
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
if (m_pluginManager.LoadPluginFromPath(path)) {
|
||||||
|
refreshList();
|
||||||
|
m_statusLabel->setText("Plugin loaded successfully");
|
||||||
|
} else {
|
||||||
|
QMessageBox::warning(&dialog, "Failed to Load Plugin",
|
||||||
|
"Could not load the selected plugin.\nCheck the console for details.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto* btnUnload = new QPushButton("Unload Selected");
|
||||||
|
connect(btnUnload, &QPushButton::clicked, [&, list, refreshList]() {
|
||||||
|
auto* item = list->currentItem();
|
||||||
|
if (!item) {
|
||||||
|
QMessageBox::information(&dialog, "No Selection", "Please select a plugin to unload.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString pluginName = item->data(Qt::UserRole).toString();
|
||||||
|
if (pluginName.isEmpty()) return;
|
||||||
|
|
||||||
|
auto reply = QMessageBox::question(&dialog, "Unload Plugin",
|
||||||
|
QString("Are you sure you want to unload '%1'?").arg(pluginName),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
if (m_pluginManager.UnloadPlugin(pluginName)) {
|
||||||
|
refreshList();
|
||||||
|
m_statusLabel->setText("Plugin unloaded");
|
||||||
|
} else {
|
||||||
|
QMessageBox::warning(&dialog, "Failed to Unload",
|
||||||
|
"Could not unload the selected plugin.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto* btnClose = new QPushButton("Close");
|
||||||
|
connect(btnClose, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||||
|
|
||||||
|
btnLayout->addWidget(btnLoad);
|
||||||
|
btnLayout->addWidget(btnUnload);
|
||||||
|
btnLayout->addStretch();
|
||||||
|
btnLayout->addWidget(btnClose);
|
||||||
|
|
||||||
|
layout->addLayout(btnLayout);
|
||||||
|
|
||||||
|
dialog.exec();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|
||||||
// ── Entry point ──
|
// ── Entry point ──
|
||||||
|
|||||||
194
src/pluginmanager.cpp
Normal file
194
src/pluginmanager.cpp
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
#include "pluginmanager.h"
|
||||||
|
#include "providerregistry.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
PluginManager::~PluginManager()
|
||||||
|
{
|
||||||
|
UnloadPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginManager::LoadPlugins()
|
||||||
|
{
|
||||||
|
// Get the Plugins directory relative to the executable
|
||||||
|
QString appDir = QCoreApplication::applicationDirPath();
|
||||||
|
QString pluginsDir = appDir + "/Plugins";
|
||||||
|
|
||||||
|
QDir dir(pluginsDir);
|
||||||
|
if (!dir.exists())
|
||||||
|
{
|
||||||
|
qWarning() << "PluginManager: Plugins directory not found:" << pluginsDir;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all DLL files
|
||||||
|
QStringList filters;
|
||||||
|
#ifdef _WIN32
|
||||||
|
filters << "*.dll";
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
filters << "*.dylib";
|
||||||
|
#else
|
||||||
|
filters << "*.so";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
dir.setNameFilters(filters);
|
||||||
|
QFileInfoList files = dir.entryInfoList(QDir::Files);
|
||||||
|
|
||||||
|
qDebug() << "PluginManager: Scanning for plugins in:" << pluginsDir;
|
||||||
|
qDebug() << "PluginManager: Found" << files.count() << "potential plugin(s)";
|
||||||
|
|
||||||
|
for (const QFileInfo& fileInfo : files)
|
||||||
|
{
|
||||||
|
LoadPlugin(fileInfo.absoluteFilePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "PluginManager: Loaded" << m_plugins.count() << "plugin(s)";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginManager::LoadPlugin(const QString& path)
|
||||||
|
{
|
||||||
|
QLibrary* library = new QLibrary(path);
|
||||||
|
|
||||||
|
// Load the library
|
||||||
|
if (!library->load())
|
||||||
|
{
|
||||||
|
qWarning() << "PluginManager: Failed to load plugin:" << path;
|
||||||
|
qWarning() << "PluginManager: Error" << library->errorString();
|
||||||
|
delete library;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the CreatePlugin function
|
||||||
|
CreatePluginFunc CreateFunc = (CreatePluginFunc)library->resolve("CreatePlugin");
|
||||||
|
if (!CreateFunc)
|
||||||
|
{
|
||||||
|
qWarning() << "PluginManager: Plugin" << path << "does not export CreatePlugin()";
|
||||||
|
library->unload();
|
||||||
|
delete library;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create plugin instance
|
||||||
|
IPlugin* plugin = CreateFunc();
|
||||||
|
if (!plugin)
|
||||||
|
{
|
||||||
|
qWarning() << "PluginManager: CreatePlugin() returned nullptr for" << path;
|
||||||
|
library->unload();
|
||||||
|
delete library;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "PluginManager: Loaded plugin:" << plugin->Name() << plugin->Version() << "by" << plugin->Author();
|
||||||
|
|
||||||
|
// Store plugin entry
|
||||||
|
m_entries.append({library, plugin});
|
||||||
|
m_plugins.append(plugin);
|
||||||
|
|
||||||
|
// Auto-register providers in global registry
|
||||||
|
if (plugin->Type() == IPlugin::ProviderPlugin)
|
||||||
|
{
|
||||||
|
IProviderPlugin* provider = static_cast<IProviderPlugin*>(plugin);
|
||||||
|
QString name = QString::fromStdString(plugin->Name());
|
||||||
|
QString identifier = name.toLower().replace(" ", "");
|
||||||
|
ProviderRegistry::instance().registerProvider(name, identifier, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<IProviderPlugin*> PluginManager::providerPlugins() const
|
||||||
|
{
|
||||||
|
QVector<IProviderPlugin*> result;
|
||||||
|
for (IPlugin* plugin : m_plugins)
|
||||||
|
{
|
||||||
|
if (plugin->Type() == IPlugin::ProviderPlugin)
|
||||||
|
{
|
||||||
|
result.append(static_cast<IProviderPlugin*>(plugin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPlugin* PluginManager::FindPlugin(const QString& name) const
|
||||||
|
{
|
||||||
|
for (IPlugin* plugin : m_plugins)
|
||||||
|
{
|
||||||
|
if (QString::fromStdString(plugin->Name()) == name)
|
||||||
|
{
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginManager::LoadPluginFromPath(const QString& path)
|
||||||
|
{
|
||||||
|
// Check if already loaded
|
||||||
|
QFileInfo fileInfo(path);
|
||||||
|
QString fileName = fileInfo.fileName();
|
||||||
|
|
||||||
|
for (const auto& entry : m_entries)
|
||||||
|
{
|
||||||
|
if (entry.library->fileName().endsWith(fileName))
|
||||||
|
{
|
||||||
|
qWarning() << "PluginManager: Plugin already loaded:" << fileName;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadPlugin(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginManager::UnloadPlugin(const QString& name)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_entries.size(); ++i)
|
||||||
|
{
|
||||||
|
if (QString::fromStdString(m_entries[i].plugin->Name()) == name)
|
||||||
|
{
|
||||||
|
qDebug() << "PluginManager: Unloading plugin:" << name;
|
||||||
|
|
||||||
|
IPlugin* plugin = m_entries[i].plugin;
|
||||||
|
|
||||||
|
// Unregister provider from global registry
|
||||||
|
if (plugin->Type() == IPlugin::ProviderPlugin)
|
||||||
|
{
|
||||||
|
QString identifier = name.toLower().replace(" ", "");
|
||||||
|
ProviderRegistry::instance().unregisterProvider(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete plugin instance
|
||||||
|
delete plugin;
|
||||||
|
|
||||||
|
// Unload library
|
||||||
|
m_entries[i].library->unload();
|
||||||
|
delete m_entries[i].library;
|
||||||
|
|
||||||
|
// Remove from lists
|
||||||
|
m_entries.remove(i);
|
||||||
|
m_plugins.remove(i);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "PluginManager: Plugin not found:" << name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginManager::UnloadPlugins()
|
||||||
|
{
|
||||||
|
// Clear provider registry
|
||||||
|
ProviderRegistry::instance().clear();
|
||||||
|
|
||||||
|
// Delete plugin instances and unload libraries
|
||||||
|
for (int i = 0; i < m_entries.size(); ++i) {
|
||||||
|
delete m_entries[i].plugin;
|
||||||
|
m_entries[i].library->unload();
|
||||||
|
delete m_entries[i].library;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.clear();
|
||||||
|
m_plugins.clear();
|
||||||
|
}
|
||||||
49
src/pluginmanager.h
Normal file
49
src/pluginmanager.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "iplugin.h"
|
||||||
|
#include <QVector>
|
||||||
|
#include <QString>
|
||||||
|
#include <QLibrary>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages plugin loading and lifecycle
|
||||||
|
*/
|
||||||
|
class PluginManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PluginManager() = default;
|
||||||
|
~PluginManager();
|
||||||
|
|
||||||
|
// Load plugins from the "Plugins" folder
|
||||||
|
void LoadPlugins();
|
||||||
|
|
||||||
|
// Get all loaded plugins
|
||||||
|
const QVector<IPlugin*>& plugins() const { return m_plugins; }
|
||||||
|
|
||||||
|
// Get plugins of a specific type
|
||||||
|
QVector<IProviderPlugin*> providerPlugins() const;
|
||||||
|
|
||||||
|
// Find plugin by name
|
||||||
|
IPlugin* FindPlugin(const QString& name) const;
|
||||||
|
|
||||||
|
// Load a single plugin from path
|
||||||
|
bool LoadPluginFromPath(const QString& path);
|
||||||
|
|
||||||
|
// Unload a specific plugin by name
|
||||||
|
bool UnloadPlugin(const QString& name);
|
||||||
|
|
||||||
|
// Unload all plugins
|
||||||
|
void UnloadPlugins();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PluginEntry
|
||||||
|
{
|
||||||
|
QLibrary* library;
|
||||||
|
IPlugin* plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<PluginEntry> m_entries;
|
||||||
|
QVector<IPlugin*> m_plugins; // Non-owning pointers for quick access
|
||||||
|
|
||||||
|
bool LoadPlugin(const QString& path);
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
ProcessPicker::ProcessPicker(QWidget *parent)
|
ProcessPicker::ProcessPicker(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, ui(new Ui::ProcessPicker)
|
, ui(new Ui::ProcessPicker)
|
||||||
|
, m_useCustomList(false)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
@@ -36,6 +37,31 @@ ProcessPicker::ProcessPicker(QWidget *parent)
|
|||||||
refreshProcessList();
|
refreshProcessList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, ui(new Ui::ProcessPicker)
|
||||||
|
, m_useCustomList(true)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
// Configure table
|
||||||
|
ui->processTable->setColumnWidth(0, 80);
|
||||||
|
ui->processTable->setColumnWidth(1, 200);
|
||||||
|
ui->processTable->horizontalHeader()->setStretchLastSection(true);
|
||||||
|
ui->processTable->setWordWrap(false);
|
||||||
|
ui->processTable->setTextElideMode(Qt::ElideLeft);
|
||||||
|
|
||||||
|
// Connect signals (no refresh button for custom lists)
|
||||||
|
ui->refreshButton->setVisible(false);
|
||||||
|
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
|
||||||
|
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
|
||||||
|
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
|
||||||
|
|
||||||
|
// Use custom process list
|
||||||
|
m_allProcesses = customProcesses;
|
||||||
|
applyFilter();
|
||||||
|
}
|
||||||
|
|
||||||
ProcessPicker::~ProcessPicker()
|
ProcessPicker::~ProcessPicker()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ProcessPicker : public QDialog
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ProcessPicker(QWidget *parent = nullptr);
|
explicit ProcessPicker(QWidget *parent = nullptr);
|
||||||
|
explicit ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent = nullptr);
|
||||||
~ProcessPicker();
|
~ProcessPicker();
|
||||||
|
|
||||||
uint32_t selectedProcessId() const;
|
uint32_t selectedProcessId() const;
|
||||||
@@ -41,6 +42,7 @@ private:
|
|||||||
uint32_t m_selectedPid = 0;
|
uint32_t m_selectedPid = 0;
|
||||||
QString m_selectedName;
|
QString m_selectedName;
|
||||||
QList<ProcessInfo> m_allProcesses;
|
QList<ProcessInfo> m_allProcesses;
|
||||||
|
bool m_useCustomList = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PROCESSPICKER_H
|
#endif // PROCESSPICKER_H
|
||||||
|
|||||||
57
src/providerregistry.cpp
Normal file
57
src/providerregistry.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#include "providerregistry.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
ProviderRegistry& ProviderRegistry::instance() {
|
||||||
|
static ProviderRegistry s_instance;
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderRegistry::registerProvider(const QString& name, const QString& identifier, IProviderPlugin* plugin) {
|
||||||
|
// Check if already registered
|
||||||
|
for (const auto& info : m_providers) {
|
||||||
|
if (info.identifier == identifier) {
|
||||||
|
qWarning() << "ProviderRegistry: Provider already registered:" << identifier;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_providers.append(ProviderInfo(name, identifier, plugin));
|
||||||
|
qDebug() << "ProviderRegistry: Registered plugin provider:" << name << "(" << identifier << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderRegistry::registerBuiltinProvider(const QString& name, const QString& identifier, BuiltinFactory factory) {
|
||||||
|
// Check if already registered
|
||||||
|
for (const auto& info : m_providers) {
|
||||||
|
if (info.identifier == identifier) {
|
||||||
|
qWarning() << "ProviderRegistry: Provider already registered:" << identifier;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_providers.append(ProviderInfo(name, identifier, factory));
|
||||||
|
qDebug() << "ProviderRegistry: Registered builtin provider:" << name << "(" << identifier << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderRegistry::unregisterProvider(const QString& identifier) {
|
||||||
|
for (int i = 0; i < m_providers.size(); ++i) {
|
||||||
|
if (m_providers[i].identifier == identifier) {
|
||||||
|
qDebug() << "ProviderRegistry: Unregistered provider:" << identifier;
|
||||||
|
m_providers.remove(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qWarning() << "ProviderRegistry: Provider not found:" << identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProviderRegistry::ProviderInfo* ProviderRegistry::findProvider(const QString& identifier) const {
|
||||||
|
for (const auto& info : m_providers) {
|
||||||
|
if (info.identifier == identifier) {
|
||||||
|
return &info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderRegistry::clear() {
|
||||||
|
m_providers.clear();
|
||||||
|
}
|
||||||
59
src/providerregistry.h
Normal file
59
src/providerregistry.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "iplugin.h"
|
||||||
|
#include <QVector>
|
||||||
|
#include <QString>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
namespace rcx { class Provider; }
|
||||||
|
class QWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global registry for data source providers
|
||||||
|
*
|
||||||
|
* Providers register themselves here so they can be listed in the Source picker.
|
||||||
|
* Supports both plugin-based providers and built-in providers.
|
||||||
|
*/
|
||||||
|
class ProviderRegistry {
|
||||||
|
public:
|
||||||
|
// Factory function for creating built-in providers
|
||||||
|
using BuiltinFactory = std::function<bool(QWidget* parent, QString* target)>;
|
||||||
|
|
||||||
|
struct ProviderInfo {
|
||||||
|
QString name; // Display name (e.g., "Process Memory")
|
||||||
|
QString identifier; // Unique ID (e.g., "process")
|
||||||
|
IProviderPlugin* plugin; // Plugin (if plugin-based)
|
||||||
|
BuiltinFactory factory; // Factory (if built-in)
|
||||||
|
bool isBuiltin;
|
||||||
|
|
||||||
|
ProviderInfo(const QString& n, const QString& id, IProviderPlugin* p)
|
||||||
|
: name(n), identifier(id), plugin(p), factory(nullptr), isBuiltin(false) {}
|
||||||
|
|
||||||
|
ProviderInfo(const QString& n, const QString& id, BuiltinFactory f)
|
||||||
|
: name(n), identifier(id), plugin(nullptr), factory(f), isBuiltin(true) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static ProviderRegistry& instance();
|
||||||
|
|
||||||
|
// Register a plugin-based provider
|
||||||
|
void registerProvider(const QString& name, const QString& identifier, IProviderPlugin* plugin);
|
||||||
|
|
||||||
|
// Register a built-in provider with a factory function
|
||||||
|
void registerBuiltinProvider(const QString& name, const QString& identifier, BuiltinFactory factory);
|
||||||
|
|
||||||
|
// Unregister a provider (called when unloading plugins)
|
||||||
|
void unregisterProvider(const QString& identifier);
|
||||||
|
|
||||||
|
// Get all registered providers
|
||||||
|
const QVector<ProviderInfo>& providers() const { return m_providers; }
|
||||||
|
|
||||||
|
// Find provider by identifier
|
||||||
|
const ProviderInfo* findProvider(const QString& identifier) const;
|
||||||
|
|
||||||
|
// Clear all providers
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ProviderRegistry() = default;
|
||||||
|
QVector<ProviderInfo> m_providers;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user