From 71943228315ae2f9b2dfaba8e043ee85cd5b39dd Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 10 Feb 2026 01:37:26 +0100 Subject: [PATCH] Add support for Qt5 --- CMakeLists.txt | 104 +++++++++----- cmake/FindQScintilla.cmake | 132 ++++++++++++++++-- cmake/FindQt5.cmake | 36 +++++ cmake/deploy.cmake | 82 +++++++++++ plugins/ProcessMemory/CMakeLists.txt | 5 +- plugins/ProcessMemory/ProcessMemoryPlugin.cpp | 7 + src/controller.cpp | 4 +- src/core.h | 6 + src/main.cpp | 28 ++-- src/mcp/mcp_bridge.cpp | 4 +- src/pluginmanager.cpp | 2 +- src/processpicker.cpp | 7 + src/providerregistry.cpp | 2 +- src/providerregistry.h | 6 +- tests/test_provider_getSymbol.cpp | 16 +-- tools/rcx-mcp-stdio.cpp | 4 + 16 files changed, 363 insertions(+), 82 deletions(-) create mode 100644 cmake/FindQt5.cmake create mode 100644 cmake/deploy.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 53aca56..45bc31d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,9 +7,25 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) -find_package(Qt6 REQUIRED COMPONENTS Widgets PrintSupport Svg Concurrent Network) - list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +# Find Qt6 or Qt5 (config mode first, then FindQt5.cmake module for auto-download) +set(_QT_COMPONENTS Core Widgets PrintSupport Svg Concurrent Network) +find_package(QT NAMES Qt6 Qt5 COMPONENTS ${_QT_COMPONENTS} QUIET) +if(NOT QT_FOUND) + find_package(Qt5 REQUIRED COMPONENTS ${_QT_COMPONENTS}) + set(QT_VERSION_MAJOR 5) +endif() +set(QT Qt${QT_VERSION_MAJOR}) +message(STATUS "Using ${QT}: ${${QT}_DIR}") + +# Qt5 on Windows needs WinExtras for HICON conversion +set(_QT_WINEXTRAS "") +if(QT_VERSION_MAJOR EQUAL 5 AND WIN32) + find_package(Qt5 REQUIRED COMPONENTS WinExtras) + set(_QT_WINEXTRAS Qt5::WinExtras) +endif() + find_package(QScintilla REQUIRED) add_executable(ReclassX @@ -49,35 +65,30 @@ add_executable(ReclassX target_include_directories(ReclassX PRIVATE src) target_link_libraries(ReclassX PRIVATE - Qt6::Widgets - Qt6::PrintSupport - Qt6::Svg - Qt6::Concurrent - Qt6::Network + ${QT}::Widgets + ${QT}::PrintSupport + ${QT}::Svg + ${QT}::Concurrent + ${QT}::Network QScintilla::QScintilla + ${_QT_WINEXTRAS} ) if(WIN32) target_link_libraries(ReclassX PRIVATE dbghelp psapi) endif() add_executable(rcx-mcp-stdio tools/rcx-mcp-stdio.cpp) -target_link_libraries(rcx-mcp-stdio PRIVATE Qt6::Core Qt6::Network) +target_link_libraries(rcx-mcp-stdio PRIVATE ${QT}::Core ${QT}::Network) + +include(deploy) add_custom_target(screenshot ALL COMMAND ReclassX --screenshot ${CMAKE_BINARY_DIR}/screenshot.png - DEPENDS ReclassX + DEPENDS ReclassX deploy WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Capturing UI screenshot with class open..." ) -add_custom_target(copy_demo ALL - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_BINARY_DIR}/demo.rcx - ${CMAKE_SOURCE_DIR}/src/examples/demo.rcx - DEPENDS screenshot - COMMENT "Copying demo.rcx to src/examples..." -) - set(_combine_script "${CMAKE_BINARY_DIR}/combine_sources.cmake") file(WRITE ${_combine_script} " set(_out \"${CMAKE_BINARY_DIR}/h_cpp_combined.txt\") @@ -107,46 +118,47 @@ add_custom_target(combined ALL include(CTest) if(BUILD_TESTING) - find_package(Qt6 REQUIRED COMPONENTS Test) + find_package(${QT} REQUIRED COMPONENTS Test) enable_testing() add_executable(test_core tests/test_core.cpp src/format.cpp src/compose.cpp) target_include_directories(test_core PRIVATE src) - target_link_libraries(test_core PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_core PRIVATE ${QT}::Core ${QT}::Test) add_test(NAME test_core COMMAND test_core) add_executable(test_format tests/test_format.cpp src/format.cpp) target_include_directories(test_format PRIVATE src) - target_link_libraries(test_format PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_format PRIVATE ${QT}::Core ${QT}::Test) add_test(NAME test_format COMMAND test_format) add_executable(test_compose tests/test_compose.cpp src/compose.cpp src/format.cpp) target_include_directories(test_compose PRIVATE src) - target_link_libraries(test_compose PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_compose PRIVATE ${QT}::Core ${QT}::Test) add_test(NAME test_compose COMMAND test_compose) add_executable(test_editor tests/test_editor.cpp src/editor.cpp src/compose.cpp src/format.cpp src/providerregistry.cpp src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_editor PRIVATE src) target_link_libraries(test_editor PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Test + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Test QScintilla::QScintilla) add_test(NAME test_editor COMMAND test_editor) add_executable(test_provider tests/test_provider.cpp) target_include_directories(test_provider PRIVATE src) - target_link_libraries(test_provider PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_provider PRIVATE ${QT}::Core ${QT}::Test) add_test(NAME test_provider COMMAND test_provider) add_executable(test_command_row tests/test_command_row.cpp) target_include_directories(test_command_row PRIVATE src) - target_link_libraries(test_command_row PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_command_row PRIVATE ${QT}::Core ${QT}::Test) add_test(NAME test_command_row COMMAND test_command_row) add_executable(test_provider_getSymbol tests/test_provider_getSymbol.cpp) target_include_directories(test_provider_getSymbol PRIVATE src) - target_link_libraries(test_provider_getSymbol PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_provider_getSymbol PRIVATE ${QT}::Core ${QT}::Test) if(WIN32) + target_compile_definitions(test_provider_getSymbol PRIVATE _WIN32) target_link_libraries(test_provider_getSymbol PRIVATE psapi) endif() add_test(NAME test_provider_getSymbol COMMAND test_provider_getSymbol) @@ -158,10 +170,10 @@ if(BUILD_TESTING) src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_controller PRIVATE src) target_link_libraries(test_controller PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test QScintilla::QScintilla) if(WIN32) - target_link_libraries(test_controller PRIVATE dbghelp psapi) + target_link_libraries(test_controller PRIVATE dbghelp psapi ${_QT_WINEXTRAS}) endif() add_test(NAME test_controller COMMAND test_controller) @@ -172,17 +184,17 @@ if(BUILD_TESTING) src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_validation PRIVATE src) target_link_libraries(test_validation PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test QScintilla::QScintilla) if(WIN32) - target_link_libraries(test_validation PRIVATE dbghelp psapi) + target_link_libraries(test_validation PRIVATE dbghelp psapi ${_QT_WINEXTRAS}) endif() add_test(NAME test_validation COMMAND test_validation) add_executable(test_generator tests/test_generator.cpp src/generator.cpp src/compose.cpp src/format.cpp) target_include_directories(test_generator PRIVATE src) - target_link_libraries(test_generator PRIVATE Qt6::Core Qt6::Test) + target_link_libraries(test_generator PRIVATE ${QT}::Core ${QT}::Test) add_test(NAME test_generator COMMAND test_generator) add_executable(test_context_menu tests/test_context_menu.cpp @@ -192,10 +204,10 @@ if(BUILD_TESTING) src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_context_menu PRIVATE src) target_link_libraries(test_context_menu PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test QScintilla::QScintilla) if(WIN32) - target_link_libraries(test_context_menu PRIVATE dbghelp psapi) + target_link_libraries(test_context_menu PRIVATE dbghelp psapi ${_QT_WINEXTRAS}) endif() add_test(NAME test_context_menu COMMAND test_context_menu) @@ -203,7 +215,7 @@ if(BUILD_TESTING) src/generator.cpp src/compose.cpp src/format.cpp) target_include_directories(test_rendered_view PRIVATE src) target_link_libraries(test_rendered_view PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Test + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Test QScintilla::QScintilla) add_test(NAME test_rendered_view COMMAND test_rendered_view) @@ -214,10 +226,10 @@ if(BUILD_TESTING) src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_new_features PRIVATE src) target_link_libraries(test_new_features PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test QScintilla::QScintilla) if(WIN32) - target_link_libraries(test_new_features PRIVATE dbghelp psapi) + target_link_libraries(test_new_features PRIVATE dbghelp psapi ${_QT_WINEXTRAS}) endif() add_test(NAME test_new_features COMMAND test_new_features) @@ -228,14 +240,30 @@ if(BUILD_TESTING) src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_type_selector PRIVATE src) target_link_libraries(test_type_selector PRIVATE - Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test - QScintilla::QScintilla dbghelp psapi) + ${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test + QScintilla::QScintilla) + if(WIN32) + target_link_libraries(test_type_selector PRIVATE dbghelp psapi ${_QT_WINEXTRAS}) + endif() add_test(NAME test_type_selector COMMAND test_type_selector) add_executable(test_theme tests/test_theme.cpp src/themes/theme.cpp src/themes/thememanager.cpp) target_include_directories(test_theme PRIVATE src) - target_link_libraries(test_theme PRIVATE Qt6::Widgets Qt6::Test) + target_link_libraries(test_theme PRIVATE ${QT}::Widgets ${QT}::Test) add_test(NAME test_theme COMMAND test_theme) + + # Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe + # that links the broadest set of Qt modules; all test exes share the same output dir) + if(TARGET ${QT}::windeployqt) + add_custom_target(deploy_tests ALL + COMMAND $ + --no-compiler-runtime --no-translations + --no-opengl-sw --no-system-d3d-compiler + $ + DEPENDS test_controller + COMMENT "Deploying Qt runtime DLLs for tests..." + ) + endif() endif() add_subdirectory(plugins/ProcessMemory) diff --git a/cmake/FindQScintilla.cmake b/cmake/FindQScintilla.cmake index 6ea44ba..ab89c87 100644 --- a/cmake/FindQScintilla.cmake +++ b/cmake/FindQScintilla.cmake @@ -1,5 +1,6 @@ set(_QSCI_ROOT "${CMAKE_SOURCE_DIR}/third_party/qscintilla") +# Try to find a pre-built library first find_path(QScintilla_INCLUDE_DIR NAMES Qsci/qsciscintilla.h PATHS "${_QSCI_ROOT}/src" "${_QSCI_ROOT}/include" @@ -7,7 +8,10 @@ find_path(QScintilla_INCLUDE_DIR ) find_library(QScintilla_LIBRARY - NAMES qscintilla2_qt6 libqscintilla2_qt6 + NAMES + qscintilla2_qt${QT_VERSION_MAJOR} libqscintilla2_qt${QT_VERSION_MAJOR} + qscintilla2_qt6 libqscintilla2_qt6 + qscintilla2_qt5 libqscintilla2_qt5 PATHS "${_QSCI_ROOT}/src/release" "${_QSCI_ROOT}/src" @@ -15,13 +19,11 @@ find_library(QScintilla_LIBRARY NO_DEFAULT_PATH ) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QScintilla DEFAULT_MSG - QScintilla_LIBRARY QScintilla_INCLUDE_DIR) - -if(QScintilla_FOUND) - set(QScintilla_INCLUDE_DIRS ${QScintilla_INCLUDE_DIR}) - set(QScintilla_LIBRARIES ${QScintilla_LIBRARY}) +if(QScintilla_LIBRARY AND QScintilla_INCLUDE_DIR) + # Use pre-built library + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(QScintilla DEFAULT_MSG + QScintilla_LIBRARY QScintilla_INCLUDE_DIR) if(NOT TARGET QScintilla::QScintilla) add_library(QScintilla::QScintilla STATIC IMPORTED) set_target_properties(QScintilla::QScintilla PROPERTIES @@ -29,4 +31,118 @@ if(QScintilla_FOUND) INTERFACE_INCLUDE_DIRECTORIES "${QScintilla_INCLUDE_DIR}" ) endif() +elseif(EXISTS "${_QSCI_ROOT}/src/qsciscintilla.cpp") + # Build from source + message(STATUS "Building QScintilla from source") + + file(GLOB _QSCI_LEXER_SOURCES "${_QSCI_ROOT}/scintilla/lexers/*.cpp") + file(GLOB _QSCI_LEXLIB_SOURCES "${_QSCI_ROOT}/scintilla/lexlib/*.cpp") + file(GLOB _QSCI_SCI_SOURCES "${_QSCI_ROOT}/scintilla/src/*.cpp") + file(GLOB _QSCI_HEADERS "${_QSCI_ROOT}/src/Qsci/*.h") + + set(_QSCI_QT_SOURCES + "${_QSCI_ROOT}/src/qsciscintilla.cpp" + "${_QSCI_ROOT}/src/qsciscintillabase.cpp" + "${_QSCI_ROOT}/src/qsciabstractapis.cpp" + "${_QSCI_ROOT}/src/qsciapis.cpp" + "${_QSCI_ROOT}/src/qscicommand.cpp" + "${_QSCI_ROOT}/src/qscicommandset.cpp" + "${_QSCI_ROOT}/src/qscidocument.cpp" + "${_QSCI_ROOT}/src/qscilexer.cpp" + "${_QSCI_ROOT}/src/qscilexerasm.cpp" + "${_QSCI_ROOT}/src/qscilexeravs.cpp" + "${_QSCI_ROOT}/src/qscilexerbash.cpp" + "${_QSCI_ROOT}/src/qscilexerbatch.cpp" + "${_QSCI_ROOT}/src/qscilexercmake.cpp" + "${_QSCI_ROOT}/src/qscilexercoffeescript.cpp" + "${_QSCI_ROOT}/src/qscilexercpp.cpp" + "${_QSCI_ROOT}/src/qscilexercsharp.cpp" + "${_QSCI_ROOT}/src/qscilexercss.cpp" + "${_QSCI_ROOT}/src/qscilexercustom.cpp" + "${_QSCI_ROOT}/src/qscilexerd.cpp" + "${_QSCI_ROOT}/src/qscilexerdiff.cpp" + "${_QSCI_ROOT}/src/qscilexeredifact.cpp" + "${_QSCI_ROOT}/src/qscilexerfortran.cpp" + "${_QSCI_ROOT}/src/qscilexerfortran77.cpp" + "${_QSCI_ROOT}/src/qscilexerhex.cpp" + "${_QSCI_ROOT}/src/qscilexerhtml.cpp" + "${_QSCI_ROOT}/src/qscilexeridl.cpp" + "${_QSCI_ROOT}/src/qscilexerintelhex.cpp" + "${_QSCI_ROOT}/src/qscilexerjava.cpp" + "${_QSCI_ROOT}/src/qscilexerjavascript.cpp" + "${_QSCI_ROOT}/src/qscilexerjson.cpp" + "${_QSCI_ROOT}/src/qscilexerlua.cpp" + "${_QSCI_ROOT}/src/qscilexermakefile.cpp" + "${_QSCI_ROOT}/src/qscilexermarkdown.cpp" + "${_QSCI_ROOT}/src/qscilexermasm.cpp" + "${_QSCI_ROOT}/src/qscilexermatlab.cpp" + "${_QSCI_ROOT}/src/qscilexernasm.cpp" + "${_QSCI_ROOT}/src/qscilexeroctave.cpp" + "${_QSCI_ROOT}/src/qscilexerpascal.cpp" + "${_QSCI_ROOT}/src/qscilexerperl.cpp" + "${_QSCI_ROOT}/src/qscilexerpostscript.cpp" + "${_QSCI_ROOT}/src/qscilexerpo.cpp" + "${_QSCI_ROOT}/src/qscilexerpov.cpp" + "${_QSCI_ROOT}/src/qscilexerproperties.cpp" + "${_QSCI_ROOT}/src/qscilexerpython.cpp" + "${_QSCI_ROOT}/src/qscilexerruby.cpp" + "${_QSCI_ROOT}/src/qscilexerspice.cpp" + "${_QSCI_ROOT}/src/qscilexersql.cpp" + "${_QSCI_ROOT}/src/qscilexersrec.cpp" + "${_QSCI_ROOT}/src/qscilexertcl.cpp" + "${_QSCI_ROOT}/src/qscilexertekhex.cpp" + "${_QSCI_ROOT}/src/qscilexertex.cpp" + "${_QSCI_ROOT}/src/qscilexerverilog.cpp" + "${_QSCI_ROOT}/src/qscilexervhdl.cpp" + "${_QSCI_ROOT}/src/qscilexerxml.cpp" + "${_QSCI_ROOT}/src/qscilexeryaml.cpp" + "${_QSCI_ROOT}/src/qscimacro.cpp" + "${_QSCI_ROOT}/src/qsciprinter.cpp" + "${_QSCI_ROOT}/src/qscistyle.cpp" + "${_QSCI_ROOT}/src/qscistyledtext.cpp" + "${_QSCI_ROOT}/src/InputMethod.cpp" + "${_QSCI_ROOT}/src/ListBoxQt.cpp" + "${_QSCI_ROOT}/src/PlatQt.cpp" + "${_QSCI_ROOT}/src/SciAccessibility.cpp" + "${_QSCI_ROOT}/src/SciClasses.cpp" + "${_QSCI_ROOT}/src/ScintillaQt.cpp" + ) + + add_library(qscintilla2 STATIC + ${_QSCI_QT_SOURCES} + ${_QSCI_HEADERS} + ${_QSCI_LEXER_SOURCES} + ${_QSCI_LEXLIB_SOURCES} + ${_QSCI_SCI_SOURCES} + ) + + target_include_directories(qscintilla2 PUBLIC + "${_QSCI_ROOT}/src" + ) + target_include_directories(qscintilla2 PRIVATE + "${_QSCI_ROOT}/scintilla/include" + "${_QSCI_ROOT}/scintilla/lexlib" + "${_QSCI_ROOT}/scintilla/src" + ) + + target_compile_definitions(qscintilla2 PRIVATE + SCINTILLA_QT + SCI_LEXER + INCLUDE_DEPRECATED_FEATURES + ) + + target_link_libraries(qscintilla2 PUBLIC + ${QT}::Widgets + ${QT}::PrintSupport + ) + + set_target_properties(qscintilla2 PROPERTIES AUTOMOC ON) + + add_library(QScintilla::QScintilla ALIAS qscintilla2) + set(QScintilla_FOUND TRUE) +else() + set(QScintilla_FOUND FALSE) + if(QScintilla_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find QScintilla (missing source and pre-built library)") + endif() endif() diff --git a/cmake/FindQt5.cmake b/cmake/FindQt5.cmake new file mode 100644 index 0000000..596e24f --- /dev/null +++ b/cmake/FindQt5.cmake @@ -0,0 +1,36 @@ +# Documentation: https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html#find-modules + +# Always try config mode for the requested components (handles repeated calls) +find_package(Qt5 COMPONENTS ${Qt5_FIND_COMPONENTS} QUIET CONFIG) + +if(Qt5_FOUND) + if(NOT Qt5_FIND_QUIETLY) + message(STATUS "Qt5 found: ${Qt5_DIR}") + endif() + return() +endif() + +if(Qt5_FIND_REQUIRED AND WIN32) + message(STATUS "Downloading Qt5...") + # Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP + if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) + endif() + include(FetchContent) + set(FETCHCONTENT_QUIET OFF) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + FetchContent_Declare(Qt5 + URL "https://github.com/x64dbg/deps/releases/download/2025.07.02/qt5.12.12-msvc2017_64.7z" + URL_HASH SHA256=770490bf09514982c8192ebde9a1fac8821108ba42b021f167bac54e85ada48a + ) + else() + FetchContent_Declare(Qt5 + URL "https://github.com/x64dbg/deps/releases/download/2025.07.02/qt5.12.12-msvc2017.7z" + URL_HASH SHA256=3ff2a58e5ed772be475643cd7bb2df3e5499d7169d794ddf1ed5df5c5e862cb6 + ) + endif() + FetchContent_MakeAvailable(Qt5) + unset(FETCHCONTENT_QUIET) + set(Qt5_ROOT ${qt5_SOURCE_DIR}) + find_package(Qt5 COMPONENTS ${Qt5_FIND_COMPONENTS} CONFIG REQUIRED) +endif() diff --git a/cmake/deploy.cmake b/cmake/deploy.cmake new file mode 100644 index 0000000..7dad1c0 --- /dev/null +++ b/cmake/deploy.cmake @@ -0,0 +1,82 @@ +# cmake/deploy.cmake - Dual-mode script for deploying Qt runtime DLLs +# +# Script mode: cmake -P deploy.cmake +# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target) + +if(CMAKE_SCRIPT_MODE_FILE) + set(TARGET_EXE ${CMAKE_ARGV3}) + set(WINDEPLOYQT ${CMAKE_ARGV4}) + get_filename_component(TARGET_DIR ${TARGET_EXE} DIRECTORY) + + # Skip if already deployed for this build + if(EXISTS "${TARGET_DIR}/.qt_deployed") + return() + endif() + + message(STATUS "Running windeployqt on ${TARGET_EXE}") + + execute_process( + COMMAND ${WINDEPLOYQT} + --pdb + --no-compiler-runtime + --no-translations + --no-opengl-sw + --no-system-d3d-compiler + --force + ${TARGET_EXE} + RESULT_VARIABLE _result + ) + + if(_result EQUAL 0) + file(WRITE "${TARGET_DIR}/.qt_deployed" "") + message(STATUS "windeployqt completed successfully") + else() + message(WARNING "windeployqt failed with exit code ${_result}") + endif() + + return() +endif() + +# ── Include mode: configure the deploy target ── + +if(NOT WIN32) + return() +endif() + +# Discover windeployqt from qmake +if(NOT TARGET ${QT}::windeployqt AND TARGET ${QT}::qmake) + get_target_property(_qt_qmake_location ${QT}::qmake IMPORTED_LOCATION) + + execute_process( + COMMAND "${_qt_qmake_location}" -query QT_INSTALL_PREFIX + RESULT_VARIABLE _return_code + OUTPUT_VARIABLE _qt_install_prefix + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + set(_windeployqt "${_qt_install_prefix}/bin/windeployqt.exe") + if(EXISTS ${_windeployqt}) + add_executable(${QT}::windeployqt IMPORTED) + set_target_properties(${QT}::windeployqt PROPERTIES + IMPORTED_LOCATION ${_windeployqt} + ) + message(STATUS "Found windeployqt: ${_windeployqt}") + else() + message(WARNING "windeployqt not found at ${_windeployqt}") + endif() +endif() + +if(TARGET ${QT}::windeployqt) + add_custom_target(deploy + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake + $ + $ + DEPENDS ReclassX + COMMENT "Deploying Qt runtime DLLs..." + ) + + # Force re-deploy on rebuild + set_target_properties(deploy PROPERTIES + ADDITIONAL_CLEAN_FILES $/.qt_deployed + ) +endif() diff --git a/plugins/ProcessMemory/CMakeLists.txt b/plugins/ProcessMemory/CMakeLists.txt index 31d0400..4bd2e9b 100644 --- a/plugins/ProcessMemory/CMakeLists.txt +++ b/plugins/ProcessMemory/CMakeLists.txt @@ -4,8 +4,7 @@ project(ProcessMemoryPlugin LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Find Qt -find_package(Qt6 REQUIRED COMPONENTS Widgets) +# Qt is found by the parent project; QT variable (Qt5 or Qt6) is inherited set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -24,7 +23,7 @@ set(PLUGIN_SOURCES add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES}) # Link Qt -target_link_libraries(ProcessMemoryPlugin PRIVATE Qt6::Widgets) +target_link_libraries(ProcessMemoryPlugin PRIVATE ${QT}::Widgets ${_QT_WINEXTRAS}) # Platform-specific linking if(WIN32) diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp index 2f04b4e..b530aea 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp @@ -10,6 +10,9 @@ #include #include #include +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(_WIN32) +#include +#endif #ifdef _WIN32 #include @@ -469,7 +472,11 @@ QVector ProcessMemoryPlugin::enumerateProcesses() SHFILEINFOW sfi = {}; if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) { if (sfi.hIcon) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon)); +#else + QPixmap pixmap = QtWin::fromHICON(sfi.hIcon); +#endif info.icon = QIcon(pixmap); DestroyIcon(sfi.hIcon); } diff --git a/src/controller.cpp b/src/controller.cpp index bf6e02d..66aa233 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -59,7 +59,7 @@ static QString crumbFor(const rcx::NodeTree& t, uint64_t nodeId) { } std::reverse(parts.begin(), parts.end()); if (parts.size() > 4) - parts = {parts.front(), QStringLiteral("\u2026"), parts[parts.size() - 2], parts.back()}; + parts = QStringList{parts.front(), QStringLiteral("\u2026"), parts[parts.size() - 2], parts.back()}; return parts.join(QStringLiteral(" \u00B7 ")); } @@ -863,7 +863,7 @@ void RcxController::applyCommand(const Command& command, bool isUndo) { } else if constexpr (std::is_same_v) { const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes; if (!m_doc->provider->writeBytes(c.addr, bytes)) - qWarning() << "WriteBytes failed at address" << Qt::hex << c.addr; + qWarning() << "WriteBytes failed at address" << QString::number(c.addr, 16); // Patch snapshot so compose sees the new value immediately if (m_snapshotProv) m_snapshotProv->patchSnapshot(c.addr, bytes.constData(), bytes.size()); diff --git a/src/core.h b/src/core.h index b1cc40b..cc4a1a9 100644 --- a/src/core.h +++ b/src/core.h @@ -31,6 +31,12 @@ enum class NodeKind : uint8_t { Struct, Array }; +} // namespace rcx (temporarily close for qHash) +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +inline uint qHash(rcx::NodeKind key, uint seed = 0) { return ::qHash(static_cast(key), seed); } +#endif +namespace rcx { // reopen + // ── Kind flags (replaces repeated Hex/Padding switches) ── enum KindFlags : uint32_t { diff --git a/src/main.cpp b/src/main.cpp index 19c5955..37c3ecb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -239,23 +239,23 @@ QIcon MainWindow::makeIcon(const QString& svgPath) { void MainWindow::createMenus() { // File auto* file = menuBar()->addMenu("&File"); - file->addAction("&New", QKeySequence::New, this, &MainWindow::newDocument); - file->addAction("New &Tab", QKeySequence(Qt::CTRL | Qt::Key_T), this, &MainWindow::newFile); - file->addAction(makeIcon(":/vsicons/folder-opened.svg"), "&Open...", QKeySequence::Open, this, &MainWindow::openFile); + file->addAction("&New", this, &MainWindow::newDocument, QKeySequence::New); + file->addAction("New &Tab", this, &MainWindow::newFile, QKeySequence(Qt::CTRL | Qt::Key_T)); + file->addAction(makeIcon(":/vsicons/folder-opened.svg"), "&Open...", this, &MainWindow::openFile, QKeySequence::Open); file->addSeparator(); - file->addAction(makeIcon(":/vsicons/save.svg"), "&Save", QKeySequence::Save, this, &MainWindow::saveFile); - file->addAction(makeIcon(":/vsicons/save-as.svg"), "Save &As...", QKeySequence::SaveAs, this, &MainWindow::saveFileAs); + file->addAction(makeIcon(":/vsicons/save.svg"), "&Save", this, &MainWindow::saveFile, QKeySequence::Save); + file->addAction(makeIcon(":/vsicons/save-as.svg"), "Save &As...", this, &MainWindow::saveFileAs, QKeySequence::SaveAs); file->addSeparator(); file->addAction(makeIcon(":/vsicons/export.svg"), "Export &C++ Header...", this, &MainWindow::exportCpp); file->addSeparator(); m_mcpAction = file->addAction("Start &MCP Server", this, &MainWindow::toggleMcp); file->addSeparator(); - file->addAction(makeIcon(":/vsicons/close.svg"), "E&xit", QKeySequence(Qt::Key_Close), this, &QMainWindow::close); + file->addAction(makeIcon(":/vsicons/close.svg"), "E&xit", this, &QMainWindow::close, QKeySequence(Qt::Key_Close)); // Edit auto* edit = menuBar()->addMenu("&Edit"); - edit->addAction(makeIcon(":/vsicons/arrow-left.svg"), "&Undo", QKeySequence::Undo, this, &MainWindow::undo); - edit->addAction(makeIcon(":/vsicons/arrow-right.svg"), "&Redo", QKeySequence::Redo, this, &MainWindow::redo); + edit->addAction(makeIcon(":/vsicons/arrow-left.svg"), "&Undo", this, &MainWindow::undo, QKeySequence::Undo); + edit->addAction(makeIcon(":/vsicons/arrow-right.svg"), "&Redo", this, &MainWindow::redo, QKeySequence::Redo); edit->addSeparator(); edit->addAction("&Type Aliases...", this, &MainWindow::showTypeAliasesDialog); @@ -304,10 +304,10 @@ void MainWindow::createMenus() { // Node auto* node = menuBar()->addMenu("&Node"); - node->addAction(makeIcon(":/vsicons/add.svg"), "&Add Field", QKeySequence(Qt::Key_Insert), this, &MainWindow::addNode); - node->addAction(makeIcon(":/vsicons/remove.svg"), "&Remove Field", QKeySequence::Delete, this, &MainWindow::removeNode); - node->addAction(makeIcon(":/vsicons/symbol-structure.svg"), "Change &Type", QKeySequence(Qt::Key_T), this, &MainWindow::changeNodeType); - node->addAction(makeIcon(":/vsicons/edit.svg"), "Re&name", QKeySequence(Qt::Key_F2), this, &MainWindow::renameNodeAction); + node->addAction(makeIcon(":/vsicons/add.svg"), "&Add Field", this, &MainWindow::addNode, QKeySequence(Qt::Key_Insert)); + node->addAction(makeIcon(":/vsicons/remove.svg"), "&Remove Field", this, &MainWindow::removeNode, QKeySequence::Delete); + node->addAction(makeIcon(":/vsicons/symbol-structure.svg"), "Change &Type", this, &MainWindow::changeNodeType, QKeySequence(Qt::Key_T)); + node->addAction(makeIcon(":/vsicons/edit.svg"), "Re&name", this, &MainWindow::renameNodeAction, QKeySequence(Qt::Key_F2)); node->addAction(makeIcon(":/vsicons/files.svg"), "D&uplicate", this, &MainWindow::duplicateNodeAction)->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D)); // Plugins @@ -659,7 +659,11 @@ void MainWindow::removeNode() { if (!primary || primary->isEditing()) return; QSet indices = primary->selectedNodeIndices(); if (indices.size() > 1) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) ctrl->batchRemoveNodes(indices.values()); +#else + ctrl->batchRemoveNodes(indices.values().toVector()); +#endif } else if (indices.size() == 1) { ctrl->removeNode(*indices.begin()); } diff --git a/src/mcp/mcp_bridge.cpp b/src/mcp/mcp_bridge.cpp index 9b62f9b..10096e5 100644 --- a/src/mcp/mcp_bridge.cpp +++ b/src/mcp/mcp_bridge.cpp @@ -791,7 +791,7 @@ QJsonObject McpBridge::toolHexRead(const QJsonObject& args) { auto* prov = tab->doc->provider.get(); if (!prov) return makeTextResult("No provider", true); - int64_t offset = args.value("offset").toInteger(); + int64_t offset = static_cast(args.value("offset").toDouble()); int length = qMin(args.value("length").toInt(64), 4096); if (args.value("baseRelative").toBool()) @@ -873,7 +873,7 @@ QJsonObject McpBridge::toolHexWrite(const QJsonObject& args) { auto* doc = tab->doc; auto* prov = doc->provider.get(); - int64_t offset = args.value("offset").toInteger(); + int64_t offset = static_cast(args.value("offset").toDouble()); QString hexStr = args.value("hexBytes").toString().remove(' '); if (args.value("baseRelative").toBool()) diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 23d351f..3c650be 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -80,7 +80,7 @@ bool PluginManager::LoadPlugin(const QString& path) return false; } - qDebug() << "PluginManager: Loaded plugin:" << plugin->Name() << plugin->Version() << "by" << plugin->Author(); + qDebug() << "PluginManager: Loaded plugin:" << plugin->Name().c_str() << plugin->Version().c_str() << "by" << plugin->Author().c_str(); // Store plugin entry m_entries.append({library, plugin}); diff --git a/src/processpicker.cpp b/src/processpicker.cpp index bdd3b94..30d84dc 100644 --- a/src/processpicker.cpp +++ b/src/processpicker.cpp @@ -11,6 +11,9 @@ #include #include #include +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +#endif #elif defined(__linux__) #include #include @@ -142,7 +145,11 @@ void ProcessPicker::enumerateProcesses() SHFILEINFOW sfi = {}; if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) { if (sfi.hIcon) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) info.icon = QIcon(QPixmap::fromImage(QImage::fromHICON(sfi.hIcon))); +#else + info.icon = QIcon(QtWin::fromHICON(sfi.hIcon)); +#endif DestroyIcon(sfi.hIcon); } } diff --git a/src/providerregistry.cpp b/src/providerregistry.cpp index e041f99..3f22af8 100644 --- a/src/providerregistry.cpp +++ b/src/providerregistry.cpp @@ -36,7 +36,7 @@ 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); + m_providers.removeAt(i); return; } } diff --git a/src/providerregistry.h b/src/providerregistry.h index 3d9a957..31401f6 100644 --- a/src/providerregistry.h +++ b/src/providerregistry.h @@ -1,6 +1,6 @@ #pragma once #include "iplugin.h" -#include +#include #include #include @@ -45,7 +45,7 @@ public: void unregisterProvider(const QString& identifier); // Get all registered providers - const QVector& providers() const { return m_providers; } + const QList& providers() const { return m_providers; } // Find provider by identifier const ProviderInfo* findProvider(const QString& identifier) const; @@ -55,5 +55,5 @@ public: private: ProviderRegistry() = default; - QVector m_providers; + QList m_providers; }; diff --git a/tests/test_provider_getSymbol.cpp b/tests/test_provider_getSymbol.cpp index 74ed490..c9719a6 100644 --- a/tests/test_provider_getSymbol.cpp +++ b/tests/test_provider_getSymbol.cpp @@ -1,14 +1,15 @@ #include #ifdef _WIN32 #include "providers/process_provider.h" - using namespace rcx; +#endif class TestProcessProviderSymbol : public QObject { Q_OBJECT private slots: +#ifdef _WIN32 void getSymbol_selfProcess() { // Attach to our own process for testing HANDLE self = GetCurrentProcess(); @@ -87,19 +88,10 @@ private slots: QString sym = prov.getSymbol(ntdllBase); QVERIFY(sym.toLower().startsWith("ntdll.dll+0x")); } -}; - -QTEST_MAIN(TestProcessProviderSymbol) -#include "test_provider_getSymbol.moc" - #else -// Non-Windows: empty test that passes -#include -class TestProcessProviderSymbol : public QObject { - Q_OBJECT -private slots: void skip() { QSKIP("ProcessProvider tests are Windows-only"); } +#endif }; + QTEST_MAIN(TestProcessProviderSymbol) #include "test_provider_getSymbol.moc" -#endif diff --git a/tools/rcx-mcp-stdio.cpp b/tools/rcx-mcp-stdio.cpp index 2c2b1d6..85993e2 100644 --- a/tools/rcx-mcp-stdio.cpp +++ b/tools/rcx-mcp-stdio.cpp @@ -47,7 +47,11 @@ int main(int argc, char* argv[]) { app.quit(); }); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QObject::connect(socket, &QLocalSocket::errorOccurred, [&](QLocalSocket::LocalSocketError err) { +#else + QObject::connect(socket, QOverload::of(&QLocalSocket::error), [&](QLocalSocket::LocalSocketError err) { +#endif fprintf(stderr, "[rcx-mcp-stdio] Socket error %d: %s\n", (int)err, socket->errorString().toUtf8().constData()); app.quit();