From 5171bea0bf64befb000fc1a0a354e34a6004f891 Mon Sep 17 00:00:00 2001 From: Nik Date: Sat, 15 Jul 2023 14:29:14 +0200 Subject: [PATCH] feat: Added cross-platform .NET scripts support (#1185) This PR intends to add support for .NET scripts that can extend ImHex's functionality in a portable and cross-platform way. --------- Co-authored-by: Justus Garbe <55301990+Nowilltolife@users.noreply.github.com> --- .github/workflows/build.yml | 39 +++- CMakeLists.txt | 2 + cmake/build_helpers.cmake | 2 +- cmake/modules/FindCoreClrEmbed.cmake | 81 +++++++ cmake/modules/PostprocessBundle.cmake | 8 +- dist/rpm/imhex.spec | 2 + lib/libimhex/source/api/plugin_manager.cpp | 12 +- main/include/init/splash_window.hpp | 2 +- main/source/init/splash_window.cpp | 55 +++-- plugins/script_loader/CMakeLists.txt | 71 ++++++ .../dotnet/AssemblyLoader/.gitignore | 6 + .../AssemblyLoader/AssemblyLoader.csproj | 17 ++ .../dotnet/AssemblyLoader/Program.cs | 70 ++++++ .../PublishProfiles/FolderProfile.pubxml | 13 ++ .../PublishProfiles/FolderProfile.pubxml.user | 10 + plugins/script_loader/dotnet/CMakeLists.txt | 28 +++ .../dotnet/post_process_runtimeconfig.cmake | 7 + .../include/loaders/dotnet/dotnet_loader.hpp | 23 ++ .../script_loader/include/loaders/loader.hpp | 39 ++++ plugins/script_loader/include/script_api.hpp | 7 + plugins/script_loader/romfs/lang/en_US.json | 10 + .../source/loaders/dotnet/dotnet_loader.cpp | 217 ++++++++++++++++++ .../source/plugin_script_loader.cpp | 118 ++++++++++ .../source/script_api/v1/bookmarks.cpp | 9 + .../source/script_api/v1/mem.cpp | 44 ++++ .../script_loader/source/script_api/v1/ui.cpp | 139 +++++++++++ .../script_loader/templates/CSharp/.gitignore | 3 + .../templates/CSharp/ImHexScript.sln | 25 ++ .../CSharp/ImHexScript/ImHex/Bookmarks.cs | 22 ++ .../CSharp/ImHexScript/ImHex/Impl/Library.cs | 7 + .../CSharp/ImHexScript/ImHex/Memory.cs | 62 +++++ .../templates/CSharp/ImHexScript/ImHex/UI.cs | 53 +++++ .../CSharp/ImHexScript/ImHexScript.csproj | 17 ++ .../templates/CSharp/ImHexScript/Program.cs | 10 + .../PublishProfiles/FolderProfile.pubxml | 13 ++ .../PublishProfiles/FolderProfile.pubxml.user | 10 + 36 files changed, 1219 insertions(+), 34 deletions(-) create mode 100644 cmake/modules/FindCoreClrEmbed.cmake create mode 100644 plugins/script_loader/CMakeLists.txt create mode 100644 plugins/script_loader/dotnet/AssemblyLoader/.gitignore create mode 100644 plugins/script_loader/dotnet/AssemblyLoader/AssemblyLoader.csproj create mode 100644 plugins/script_loader/dotnet/AssemblyLoader/Program.cs create mode 100644 plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml.user create mode 100644 plugins/script_loader/dotnet/CMakeLists.txt create mode 100644 plugins/script_loader/dotnet/post_process_runtimeconfig.cmake create mode 100644 plugins/script_loader/include/loaders/dotnet/dotnet_loader.hpp create mode 100644 plugins/script_loader/include/loaders/loader.hpp create mode 100644 plugins/script_loader/include/script_api.hpp create mode 100644 plugins/script_loader/romfs/lang/en_US.json create mode 100644 plugins/script_loader/source/loaders/dotnet/dotnet_loader.cpp create mode 100644 plugins/script_loader/source/plugin_script_loader.cpp create mode 100644 plugins/script_loader/source/script_api/v1/bookmarks.cpp create mode 100644 plugins/script_loader/source/script_api/v1/mem.cpp create mode 100644 plugins/script_loader/source/script_api/v1/ui.cpp create mode 100644 plugins/script_loader/templates/CSharp/.gitignore create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript.sln create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/ImHex/Bookmarks.cs create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/ImHex/Impl/Library.cs create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/ImHex/Memory.cs create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/ImHex/UI.cs create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/ImHexScript.csproj create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/Program.cs create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 plugins/script_loader/templates/CSharp/ImHexScript/Properties/PublishProfiles/FolderProfile.pubxml.user diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37fe0e645..fb5bbf9cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,6 +60,11 @@ jobs: libbacktrace:p ninja:p + - name: ⬇️ Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + - name: 📜 Set version variable run: | echo "IMHEX_VERSION=`cat VERSION`" >> $GITHUB_ENV @@ -164,6 +169,11 @@ jobs: run: | brew install glfw + - name: ⬇️ Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + - name: 🧰 Checkout glfw if: ${{matrix.custom_glfw}} uses: actions/checkout@v3 @@ -243,8 +253,8 @@ jobs: options: --privileged steps: - - name: ⬇️ Install git - run: apt update && apt install -y git + - name: ⬇️ Install setup dependencies + run: apt update && apt install -y git curl - name: 🧰 Checkout uses: actions/checkout@v3 @@ -270,6 +280,11 @@ jobs: apt update bash dist/get_deps_debian.sh + - name: ⬇️ Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + # Ubuntu cmake build - name: 🛠️ Build run: | @@ -280,7 +295,7 @@ jobs: mkdir -p build cd build CC=gcc-12 CXX=g++-12 cmake -G "Ninja" \ - -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ + -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ -DCMAKE_INSTALL_PREFIX="/usr" \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ @@ -290,6 +305,7 @@ jobs: -DIMHEX_COMMIT_BRANCH="${{ env.COMMIT_BRANCH }}" \ -DIMHEX_ENABLE_LTO=ON \ -DIMHEX_USE_GTK_FILE_PICKER=ON \ + -DDOTNET_EXECUTABLE="dotnet" \ .. DESTDIR=DebDir ninja install @@ -344,6 +360,11 @@ jobs: sudo chmod +x /usr/local/bin/appimagetool sudo pip3 install git+https://github.com/iTrooz/appimage-builder@dpkg-package-versions + - name: ⬇️ Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + - name: 📜 Set version variable run: | echo "IMHEX_VERSION=`cat VERSION`" >> $GITHUB_ENV @@ -362,7 +383,7 @@ jobs: -DIMHEX_COMMIT_HASH_SHORT="${GITHUB_SHA::7}" \ -DIMHEX_COMMIT_HASH_LONG="${GITHUB_SHA}" \ -DIMHEX_COMMIT_BRANCH="${GITHUB_REF##*/}" \ - -DIMHEX_ENABLE_LTO=ON \ + -DIMHEX_ENABLE_LTO=ON \ -DIMHEX_PLUGINS_IN_SHARE=ON \ -DIMHEX_USE_BUNDLED_CA=ON \ .. @@ -412,6 +433,11 @@ jobs: run: | dist/get_deps_archlinux.sh --noconfirm + - name: ⬇️ Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + - name: 📜 Setup ccache uses: hendrikmuhs/ccache-action@v1.2 with: @@ -536,6 +562,11 @@ jobs: fedpkg \ ccache + - name: ⬇️ Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + - name: 📜 Setup ccache uses: hendrikmuhs/ccache-action@v1.2.5 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index a3b11382e..3ba935bcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ option(IMHEX_PATTERNS_PULL_MASTER "Download latest files from master branch of t option(IMHEX_IGNORE_BAD_COMPILER "Allow compiling with an unsupported compiler" OFF) option(IMHEX_USE_GTK_FILE_PICKER "Use GTK file picker instead of xdg-desktop-portals" OFF) option(IMHEX_DISABLE_STACKTRACE "Disables support for printing stack traces" OFF) +option(IMHEX_BUNDLE_DOTNET "Bundle .NET runtime" ON) option(IMHEX_ENABLE_LTO "Enables Link Time Optimizations if possible" OFF) option(IMHEX_USE_DEFAULT_BUILD_SETTINGS "Use default build settings" OFF) option(IMHEX_STRICT_WARNINGS "Enable most available warnings and treat them as errors" ON) @@ -40,6 +41,7 @@ verifyCompiler() set(PLUGINS builtin windows + script_loader ) # Add various defines diff --git a/cmake/build_helpers.cmake b/cmake/build_helpers.cmake index 90379db10..8fb2475fd 100644 --- a/cmake/build_helpers.cmake +++ b/cmake/build_helpers.cmake @@ -24,7 +24,7 @@ macro(addDefines) set(IMHEX_VERSION_STRING ${IMHEX_VERSION_STRING}-Debug) add_compile_definitions(DEBUG _GLIBCXX_DEBUG _GLIBCXX_VERBOSE) elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - set(IMHEX_VERSION_STRING ${IMHEX_VERSION_STRING}-RelWithDebInfo) + set(IMHEX_VERSION_STRING ${IMHEX_VERSION_STRING}) add_compile_definitions(NDEBUG) elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") set(IMHEX_VERSION_STRING ${IMHEX_VERSION_STRING}-MinSizeRel) diff --git a/cmake/modules/FindCoreClrEmbed.cmake b/cmake/modules/FindCoreClrEmbed.cmake new file mode 100644 index 000000000..41ea53a70 --- /dev/null +++ b/cmake/modules/FindCoreClrEmbed.cmake @@ -0,0 +1,81 @@ +set(CoreClrEmbed_FOUND FALSE) + +set(CORECLR_ARCH "linux-x64") +set(CORECLR_SUBARCH "x64") +if (WIN32) + set(CORECLR_ARCH "win-x64") +endif() +if (UNIX) + if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + set(CORECLR_ARCH "linux-arm64") + set(CORECLR_SUBARCH "arm64") + endif() +endif() + +if (NOT DOTNET_EXECUTABLE) + set(DOTNET_EXECUTABLE dotnet) +endif () + +set(CORECLR_VERSION "7.0") + +execute_process(COMMAND ${DOTNET_EXECUTABLE} "--list-runtimes" OUTPUT_VARIABLE CORECLR_LIST_RUNTIMES_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) +if (CORECLR_LIST_RUNTIMES_OUTPUT STREQUAL "") + message(STATUS "Unable to find any .NET runtimes") + return() +endif () + +set(_ALL_RUNTIMES ${CORECLR_LIST_RUNTIMES_OUTPUT}) +string(REPLACE "\n" ";" _ALL_RUNTIMES_LIST ${_ALL_RUNTIMES}) + +foreach(X ${_ALL_RUNTIMES_LIST}) + string(REGEX MATCH "Microsoft\.NETCore\.App ([0-9]+)\.([0-9]+)\.([a-zA-Z0-9.-]+) [\[](.*)Microsoft\.NETCore\.App[\]]" + CORECLR_VERSION_REGEX_MATCH ${X}) + + set(_RUNTIME_VERSION ${CMAKE_MATCH_1}.${CMAKE_MATCH_2}) + + if (CMAKE_MATCH_1 AND CMAKE_MATCH_4) + if (${_RUNTIME_VERSION} STREQUAL ${CORECLR_VERSION}) + set(CORECLR_RUNTIME_VERSION ${_RUNTIME_VERSION}) + set(CORECLR_RUNTIME_VERSION_FULL ${CORECLR_VERSION}.${CMAKE_MATCH_3}) + set(CORECLR_RUNTIME_ROOT_PATH ${CMAKE_MATCH_4}) + message(STATUS "Found matching .NET runtime version '${CORECLR_RUNTIME_VERSION_FULL}' path='${CORECLR_RUNTIME_ROOT_PATH}'") + endif() + endif() +endforeach() + +if (CORECLR_RUNTIME_ROOT_PATH) + get_filename_component(CORECLR_RUNTIME_ROOT_PATH ${CORECLR_RUNTIME_ROOT_PATH} DIRECTORY) +endif() +set(CoreClrEmbed_ROOT_PATH "${CORECLR_RUNTIME_ROOT_PATH}") + + +file(GLOB _CORECLR_HOST_ARCH_PATH "${CORECLR_RUNTIME_ROOT_PATH}/packs/Microsoft.NETCore.App.Host.*-${CORECLR_SUBARCH}") +if (_CORECLR_HOST_ARCH_PATH) + get_filename_component(_CORECLR_HOST_ARCH_FILENAME ${_CORECLR_HOST_ARCH_PATH} NAME) + string(REPLACE "Microsoft.NETCore.App.Host." "" _CORECLR_COMPUTED_ARCH "${_CORECLR_HOST_ARCH_FILENAME}") + if (_CORECLR_COMPUTED_ARCH) + set(CORECLR_ARCH "${_CORECLR_COMPUTED_ARCH}") + endif() +endif() + +set(CORECLR_HOST_BASE_PATH "${CORECLR_RUNTIME_ROOT_PATH}/packs/Microsoft.NETCore.App.Host.${CORECLR_ARCH}/${CORECLR_RUNTIME_VERSION_FULL}") +file(GLOB _CORECLR_FOUND_PATH ${CORECLR_HOST_BASE_PATH}) +if (_CORECLR_FOUND_PATH) + set(CORECLR_NETHOST_ROOT "${_CORECLR_FOUND_PATH}/runtimes/${CORECLR_ARCH}/native") +endif() + +find_library(CoreClrEmbed_LIBRARY nethost PATHS + ${CORECLR_NETHOST_ROOT} +) +find_path(CoreClrEmbed_INCLUDE_DIR nethost.h PATHS + ${CORECLR_NETHOST_ROOT} +) +find_file(CoreClrEmbed_SHARED_LIBRARY nethost.dll nethost.so libnethost.so nethost.dylib libnethost.dylib PATHS + ${CORECLR_NETHOST_ROOT}) + +if (CoreClrEmbed_INCLUDE_DIR AND CoreClrEmbed_LIBRARY) + set(CoreClrEmbed_FOUND TRUE) + set(CoreClrEmbed_LIBRARIES "${CoreClrEmbed_LIBRARY}") + set(CoreClrEmbed_SHARED_LIBRARIES "${CoreClrEmbed_SHARED_LIBRARY}") + set(CoreClrEmbed_INCLUDE_DIRS "${CoreClrEmbed_INCLUDE_DIR}") +endif() \ No newline at end of file diff --git a/cmake/modules/PostprocessBundle.cmake b/cmake/modules/PostprocessBundle.cmake index 05b83470e..09a760827 100644 --- a/cmake/modules/PostprocessBundle.cmake +++ b/cmake/modules/PostprocessBundle.cmake @@ -15,7 +15,9 @@ if(CMAKE_GENERATOR) set(_POSTPROCESS_BUNDLE_MODULE_LOCATION "${CMAKE_CURRENT_LIST_FILE}") function(postprocess_bundle out_target in_target) add_custom_command(TARGET ${out_target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -DBUNDLE_PATH="$/../.." -DCODE_SIGN_CERTIFICATE_ID="${CODE_SIGN_CERTIFICATE_ID}" + COMMAND ${CMAKE_COMMAND} -DBUNDLE_PATH="$/../.." + -DCODE_SIGN_CERTIFICATE_ID="${CODE_SIGN_CERTIFICATE_ID}" + -DEXTRA_BUNDLE_LIBRARY_PATHS="${EXTRA_BUNDLE_LIBRARY_PATHS}" -P "${_POSTPROCESS_BUNDLE_MODULE_LOCATION}" ) endfunction() @@ -29,13 +31,13 @@ message(STATUS "Fixing up application bundle: ${BUNDLE_PATH}") # Make sure to fix up any included ImHex plugin. file(GLOB_RECURSE extra_libs "${BUNDLE_PATH}/Contents/MacOS/plugins/*.hexplug") -message(STATUS "Fixing up application bundle: ${extra_dirs}") # BundleUtilities doesn't support DYLD_FALLBACK_LIBRARY_PATH behavior, which # makes it sometimes break on libraries that do weird things with @rpath. Specify # equivalent search directories until https://gitlab.kitware.com/cmake/cmake/issues/16625 # is fixed and in our minimum CMake version. -set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib") +set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib" ${EXTRA_BUNDLE_LIBRARY_PATHS}) +message(STATUS "Fixing up application bundle: ${extra_dirs}") # BundleUtilities is overly verbose, so disable most of its messages function(message) diff --git a/dist/rpm/imhex.spec b/dist/rpm/imhex.spec index a63c0f5ad..1fb4d78da 100644 --- a/dist/rpm/imhex.spec +++ b/dist/rpm/imhex.spec @@ -26,6 +26,7 @@ BuildRequires: llvm-devel BuildRequires: mbedtls-devel BuildRequires: yara-devel BuildRequires: nativefiledialog-extended-devel +BuildRequires: dotnet-sdk-7.0 %if 0%{?rhel} BuildRequires: gcc-toolset-12 %endif @@ -78,6 +79,7 @@ CXXFLAGS+=" -std=gnu++2b" -D USE_SYSTEM_YARA=ON \ -D USE_SYSTEM_NFD=ON \ -D IMHEX_USE_GTK_FILE_PICKER=ON \ + -D IMHEX_BUNDLE_DOTNET=OFF \ # when capstone >= 5.x is released we should be able to build against \ # system libs of it \ # -D USE_SYSTEM_CAPSTONE=ON diff --git a/lib/libimhex/source/api/plugin_manager.cpp b/lib/libimhex/source/api/plugin_manager.cpp index 7266fd54c..2dd48aff1 100644 --- a/lib/libimhex/source/api/plugin_manager.cpp +++ b/lib/libimhex/source/api/plugin_manager.cpp @@ -15,7 +15,7 @@ namespace hex { #if defined(OS_WINDOWS) this->m_handle = LoadLibraryW(path.c_str()); - if (this->m_handle == nullptr) { + if (this->m_handle == INVALID_HANDLE_VALUE || this->m_handle == nullptr) { log::error("LoadLibraryW failed: {}!", std::system_category().message(::GetLastError())); return; } @@ -76,6 +76,8 @@ namespace hex { if (this->m_handle == nullptr) return false; + const auto pluginName = wolv::util::toUTF8String(this->m_path.filename()); + const auto requestedVersion = getCompatibleVersion(); if (requestedVersion != ImHexApi::System::getImHexVersion()) { if (requestedVersion.empty()) { @@ -87,7 +89,13 @@ namespace hex { } if (this->m_initializePluginFunction != nullptr) { - this->m_initializePluginFunction(); + try { + this->m_initializePluginFunction(); + } catch (const std::exception &e) { + log::error("Plugin '{}' threw an exception on init: {}", pluginName, e.what()); + } catch (...) { + log::error("Plugin '{}' threw an exception on init", pluginName); + } } else { return false; } diff --git a/main/include/init/splash_window.hpp b/main/include/init/splash_window.hpp index e1c2a2c93..82880ca03 100644 --- a/main/include/init/splash_window.hpp +++ b/main/include/init/splash_window.hpp @@ -27,7 +27,7 @@ namespace hex::init { GLFWwindow *m_window; std::mutex m_progressMutex; std::atomic m_progress = 0; - std::string m_currTaskName; + std::list m_currTaskNames; void initGLFW(); void initImGui(); diff --git a/main/source/init/splash_window.cpp b/main/source/init/splash_window.cpp index 2b838c0f9..455d9f8b3 100644 --- a/main/source/init/splash_window.cpp +++ b/main/source/init/splash_window.cpp @@ -59,34 +59,41 @@ namespace hex::init { std::atomic tasksCompleted = 0; for (const auto &[name, task, async] : this->m_tasks) { auto runTask = [&, task = task, name = name] { - { - std::lock_guard guard(this->m_progressMutex); - this->m_currTaskName = name; - } + try { + decltype(this->m_currTaskNames)::iterator taskNameIter; + { + std::lock_guard guard(this->m_progressMutex); + this->m_currTaskNames.push_back(name + "..."); + taskNameIter = std::prev(this->m_currTaskNames.end()); + } - ON_SCOPE_EXIT { - tasksCompleted++; - this->m_progress = float(tasksCompleted) / this->m_tasks.size(); - }; + ON_SCOPE_EXIT { + tasksCompleted++; + this->m_progress = float(tasksCompleted) / this->m_tasks.size(); + }; - auto startTime = std::chrono::high_resolution_clock::now(); - if (!task()) + auto startTime = std::chrono::high_resolution_clock::now(); + if (!task()) + status = false; + auto endTime = std::chrono::high_resolution_clock::now(); + + log::info("Task '{}' finished in {} ms", name, std::chrono::duration_cast(endTime-startTime).count()); + + { + std::lock_guard guard(this->m_progressMutex); + this->m_currTaskNames.erase(taskNameIter); + } + } catch (std::exception &e) { + log::error("Init task '{}' threw an exception: {}", name, e.what()); status = false; - auto endTime = std::chrono::high_resolution_clock::now(); - - log::info("Task '{}' finished in {} ms", name, std::chrono::duration_cast(endTime-startTime).count()); + } }; - try { - if (async) { - TaskManager::createBackgroundTask(name, [runTask](auto&){ runTask(); }); - } else { - runTask(); - } - } catch (std::exception &e) { - log::error("Init task '{}' threw an exception: {}", name, e.what()); - status = false; + if (async) { + TaskManager::createBackgroundTask(name, [runTask](auto&){ runTask(); }); + } else { + runTask(); } } @@ -146,7 +153,9 @@ namespace hex::init { { std::lock_guard guard(this->m_progressMutex); drawList->AddRectFilled(ImVec2(0, splashTexture.getSize().y - 5) * scale, ImVec2(splashTexture.getSize().x * this->m_progress, splashTexture.getSize().y) * scale, 0xFFFFFFFF); - drawList->AddText(ImVec2(15, splashTexture.getSize().y - 25) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("[{}] {}...", "|/-\\"[ImU32(ImGui::GetTime() * 15) % 4], this->m_currTaskName).c_str()); + + if (!this->m_currTaskNames.empty()) + drawList->AddText(ImVec2(15, splashTexture.getSize().y - 25) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("[{}] {}", "|/-\\"[ImU32(ImGui::GetTime() * 15) % 4], fmt::join(this->m_currTaskNames, " | ")).c_str()); } // Render the frame diff --git a/plugins/script_loader/CMakeLists.txt b/plugins/script_loader/CMakeLists.txt new file mode 100644 index 000000000..de4698677 --- /dev/null +++ b/plugins/script_loader/CMakeLists.txt @@ -0,0 +1,71 @@ +cmake_minimum_required(VERSION 3.16) + +# Change this to the name of your plugin # +project(script_loader) + +# Add your source files here # + +find_package(CoreClrEmbed) + +add_library(${PROJECT_NAME} SHARED + source/plugin_script_loader.cpp +) +# Add additional include directories here # +target_include_directories(${PROJECT_NAME} PRIVATE include) +# Add additional libraries here # +target_link_libraries(${PROJECT_NAME} PRIVATE libimhex ${FMT_LIBRARIES}) + +if (CoreClrEmbed_FOUND) + add_library(nethost SHARED IMPORTED) + target_include_directories(nethost INTERFACE "${CoreClrEmbed_INCLUDE_DIRS}") + get_filename_component(CoreClrEmbed_FOLDER ${CoreClrEmbed_SHARED_LIBRARIES} DIRECTORY) + set_target_properties(nethost + PROPERTIES + IMPORTED_IMPLIB ${CoreClrEmbed_SHARED_LIBRARIES} + IMPORTED_LOCATION ${CoreClrEmbed_LIBRARIES} + BUILD_RPATH ${CoreClrEmbed_FOLDER} + INSTALL_RPATH ${CoreClrEmbed_FOLDER}) + + target_link_directories(${PROJECT_NAME} PRIVATE ${CoreClrEmbed_FOLDER}) + target_include_directories(${PROJECT_NAME} PRIVATE ${CoreClrEmbed_INCLUDE_DIRS}) + target_compile_definitions(${PROJECT_NAME} PRIVATE DOTNET_PLUGINS=1) + target_sources(${PROJECT_NAME} PRIVATE + source/loaders/dotnet/dotnet_loader.cpp + + source/script_api/v1/mem.cpp + source/script_api/v1/bookmarks.cpp + source/script_api/v1/ui.cpp + ) + + set(EXTRA_BUNDLE_LIBRARY_PATHS "${CoreClrEmbed_FOLDER}" PARENT_SCOPE) + + if (IMHEX_BUNDLE_DOTNET) + install(FILES ${CoreClrEmbed_SHARED_LIBRARIES} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif () + + add_subdirectory(dotnet) + add_dependencies(${PROJECT_NAME} AssemblyLoader) + +endif () + + +# ---- No need to change anything from here downwards unless you know what you're doing ---- # + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_SHARED_LIBRARY_PREFIX "") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".hexplug") + +if (WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--allow-multiple-definition -fvisibility=hidden") +endif() + +target_compile_definitions(${PROJECT_NAME} PRIVATE IMHEX_PROJECT_NAME="${PROJECT_NAME}") +target_compile_definitions(${PROJECT_NAME} PRIVATE IMHEX_VERSION="${IMHEX_VERSION_STRING}") +set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) +setupCompilerFlags(${PROJECT_NAME}) + +set(LIBROMFS_RESOURCE_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/romfs) +set(LIBROMFS_PROJECT_NAME ${PROJECT_NAME}) +add_subdirectory(../../lib/external/libromfs ${CMAKE_CURRENT_BINARY_DIR}/libromfs) +set_target_properties(${LIBROMFS_LIBRARY} PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBROMFS_LIBRARY}) \ No newline at end of file diff --git a/plugins/script_loader/dotnet/AssemblyLoader/.gitignore b/plugins/script_loader/dotnet/AssemblyLoader/.gitignore new file mode 100644 index 000000000..abb902790 --- /dev/null +++ b/plugins/script_loader/dotnet/AssemblyLoader/.gitignore @@ -0,0 +1,6 @@ +.vs/ +bin/ +obj/ +*.dll +*.pdb +*.json \ No newline at end of file diff --git a/plugins/script_loader/dotnet/AssemblyLoader/AssemblyLoader.csproj b/plugins/script_loader/dotnet/AssemblyLoader/AssemblyLoader.csproj new file mode 100644 index 000000000..1623914df --- /dev/null +++ b/plugins/script_loader/dotnet/AssemblyLoader/AssemblyLoader.csproj @@ -0,0 +1,17 @@ + + + + Library + net7.0 + enable + enable + AssemblyLoader + True + true + + + + true + + + diff --git a/plugins/script_loader/dotnet/AssemblyLoader/Program.cs b/plugins/script_loader/dotnet/AssemblyLoader/Program.cs new file mode 100644 index 000000000..c25936c10 --- /dev/null +++ b/plugins/script_loader/dotnet/AssemblyLoader/Program.cs @@ -0,0 +1,70 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace ImHex +{ + + public class EntryPoint + { + + public static int ExecuteScript(IntPtr arg, int argLength) + { + try + { + return ExecuteScript(Marshal.PtrToStringUTF8(arg, argLength)) ? 0 : 1; + } + catch (Exception e) + { + Console.WriteLine("[.NET Script] Exception in AssemblyLoader: " + e.Message); + return 1; + } + } + + + private static bool ExecuteScript(string path) + { + AssemblyLoadContext? context = new("ScriptDomain_" + Path.GetFileNameWithoutExtension(path), true); + + try + { + var assembly = context.LoadFromStream(new MemoryStream(File.ReadAllBytes(path))); + + var entryPointType = assembly.GetType("Script"); + if (entryPointType == null) + { + Console.WriteLine("[.NET Script] Failed to find Script type"); + return false; + } + + var entryPointMethod = entryPointType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public); + if (entryPointMethod == null) + { + Console.WriteLine("[.NET Script] Failed to find ScriptMain method"); + return false; + } + + entryPointMethod.Invoke(null, null); + } + catch (Exception e) + { + Console.WriteLine("[.NET Script] Exception in AssemblyLoader: " + e.Message); + return false; + } + finally + { + context.Unload(); + context = null; + + for (int i = 0; i < 10; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + + return true; + } + + } +} diff --git a/plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml b/plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 000000000..34e4f3aff --- /dev/null +++ b/plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + Release + Any CPU + bin\Release\net7.0\publish\ + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml.user b/plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100644 index 000000000..66d19ff68 --- /dev/null +++ b/plugins/script_loader/dotnet/AssemblyLoader/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,10 @@ + + + + + True|2023-06-16T15:24:52.9876162Z; + + + \ No newline at end of file diff --git a/plugins/script_loader/dotnet/CMakeLists.txt b/plugins/script_loader/dotnet/CMakeLists.txt new file mode 100644 index 000000000..61d4a2b8e --- /dev/null +++ b/plugins/script_loader/dotnet/CMakeLists.txt @@ -0,0 +1,28 @@ +project(dotnet_assemblies) + +function(add_dotnet_assembly name) + set(OUTPUT_DLL ${CMAKE_CURRENT_BINARY_DIR}/../../${name}.dll) + set(OUTPUT_RUNTIMECONFIG ${CMAKE_CURRENT_BINARY_DIR}/../../${name}.runtimeconfig.json) + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../${name}.dll + COMMAND ${DOTNET_EXECUTABLE} build ${CMAKE_CURRENT_SOURCE_DIR}/${name}/${name}.csproj --nologo -c Release -o ${CMAKE_CURRENT_BINARY_DIR}/../.. && ${CMAKE_COMMAND} -DOUTPUT_RUNTIMECONFIG="${OUTPUT_RUNTIMECONFIG}" -P ${CMAKE_CURRENT_SOURCE_DIR}/post_process_runtimeconfig.cmake + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${name}/${name}.csproj + COMMENT "Building ${name}.dll" + ) + + + add_custom_target(${name} ALL DEPENDS ${OUTPUT_DLL}) + + install(FILES + ${OUTPUT_DLL} + ${OUTPUT_RUNTIMECONFIG} + DESTINATION + ${PLUGINS_INSTALL_LOCATION} + ) + + file(GLOB_RECURSE sources ${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cs) + target_sources(${name} PRIVATE ${sources}) +endfunction() + +add_dotnet_assembly(AssemblyLoader) \ No newline at end of file diff --git a/plugins/script_loader/dotnet/post_process_runtimeconfig.cmake b/plugins/script_loader/dotnet/post_process_runtimeconfig.cmake new file mode 100644 index 000000000..d80deb1e5 --- /dev/null +++ b/plugins/script_loader/dotnet/post_process_runtimeconfig.cmake @@ -0,0 +1,7 @@ +file(READ ${OUTPUT_RUNTIMECONFIG} FILE_CONTENTS) + +set(VERSION_REGEX [["version": "([0-9]+\.[0-9]+\.[0-9]+)"]]) +set(REPLACE_VALUE [["version": "7.0.0"]]) +string(REGEX REPLACE "${VERSION_REGEX}" ${REPLACE_VALUE} FILE_CONTENTS_OUT "${FILE_CONTENTS}") + +file(WRITE ${OUTPUT_RUNTIMECONFIG} "${FILE_CONTENTS_OUT}") \ No newline at end of file diff --git a/plugins/script_loader/include/loaders/dotnet/dotnet_loader.hpp b/plugins/script_loader/include/loaders/dotnet/dotnet_loader.hpp new file mode 100644 index 000000000..7cd7cdc31 --- /dev/null +++ b/plugins/script_loader/include/loaders/dotnet/dotnet_loader.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#include + +namespace hex::script::loader { + + class DotNetLoader : public ScriptLoader { + public: + DotNetLoader() = default; + ~DotNetLoader() override = default; + + bool initialize() override; + bool loadAll() override; + + private: + std::function m_loadAssembly; + }; + +} \ No newline at end of file diff --git a/plugins/script_loader/include/loaders/loader.hpp b/plugins/script_loader/include/loaders/loader.hpp new file mode 100644 index 000000000..1a664f8e1 --- /dev/null +++ b/plugins/script_loader/include/loaders/loader.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace hex::script::loader { + + struct Script { + std::string name; + std::function entryPoint; + }; + + class ScriptLoader { + public: + ScriptLoader() = default; + virtual ~ScriptLoader() = default; + + virtual bool initialize() = 0; + virtual bool loadAll() = 0; + + void addScript(std::string name, std::function entryPoint) { + this->m_scripts.emplace_back(std::move(name), std::move(entryPoint)); + } + + const auto& getScripts() const { + return this->m_scripts; + } + + protected: + void clearScripts() { + this->m_scripts.clear(); + } + + private: + std::vector