From 8267aad79e3ed9a202da42a47d459a72ee490f4c Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sun, 7 Dec 2025 16:24:36 +0100 Subject: [PATCH] feat: Add new Command Line data source --- plugins/builtin/CMakeLists.txt | 1 + .../content/providers/command_provider.hpp | 54 ++++ plugins/builtin/romfs/lang/en_US.json | 10 + plugins/builtin/source/content/providers.cpp | 6 +- .../content/providers/command_provider.cpp | 284 ++++++++++++++++++ 5 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 plugins/builtin/include/content/providers/command_provider.hpp create mode 100644 plugins/builtin/source/content/providers/command_provider.cpp diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index 2217b4936..12ceb3fb6 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -68,6 +68,7 @@ add_imhex_plugin( source/content/providers/base64_provider.cpp source/content/providers/view_provider.cpp source/content/providers/udp_provider.cpp + source/content/providers/command_provider.cpp source/content/tools/ascii_table.cpp source/content/tools/base_converter.cpp diff --git a/plugins/builtin/include/content/providers/command_provider.hpp b/plugins/builtin/include/content/providers/command_provider.hpp new file mode 100644 index 000000000..3bca3cbfc --- /dev/null +++ b/plugins/builtin/include/content/providers/command_provider.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace hex::plugin::builtin { + + class CommandProvider : public prv::CachedProvider, + public prv::IProviderLoadInterface { + public: + CommandProvider(); + ~CommandProvider() override = default; + + [[nodiscard]] bool isAvailable() const override; + [[nodiscard]] bool isReadable() const override; + [[nodiscard]] bool isWritable() const override; + [[nodiscard]] bool isResizable() const override; + [[nodiscard]] bool isSavable() const override; + + void readFromSource(u64 offset, void *buffer, size_t size) override; + void writeToSource(u64 offset, const void *buffer, size_t size) override; + [[nodiscard]] u64 getSourceSize() const override; + + void save() override; + + [[nodiscard]] std::string getName() const override; + + [[nodiscard]] bool open() override; + void close() override; + + bool drawLoadInterface() override; + + void loadSettings(const nlohmann::json &settings) override; + [[nodiscard]] nlohmann::json storeSettings(nlohmann::json settings) const override; + + [[nodiscard]] UnlocalizedString getTypeName() const override { + return "hex.builtin.provider.command"; + } + + [[nodiscard]] const char* getIcon() const override { + return ICON_VS_TERMINAL_CMD; + } + + protected: + std::string m_name; + std::string m_readCommand, m_writeCommand, m_sizeCommand, m_resizeCommand, m_saveCommand; + bool m_open = false; + }; + +} diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index ed6c9d8f3..c64ebac74 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -420,6 +420,16 @@ "hex.builtin.provider.tooltip.show_more": "Hold SHIFT for more information", "hex.builtin.provider.error.open": "Failed to open data provider: {}", "hex.builtin.provider.base64": "Base64 File", + "hex.builtin.provider.command": "Terminal Command", + "hex.builtin.provider.command.name": "Command {0}", + "hex.builtin.provider.command.load.name": "Name", + "hex.builtin.provider.command.load.hint": "Enter commands to be executed for specific functions.\n\nThe {address} and {size} placeholders will be replaced with the respective value", + "hex.builtin.provider.command.load.read_command": "Read Data Command", + "hex.builtin.provider.command.load.write_command": "Write Data Command", + "hex.builtin.provider.command.load.size_command": "Get Data Size Command", + "hex.builtin.provider.command.load.resize_command": "Resize Data Command", + "hex.builtin.provider.command.load.save_command": "Save Data Command", + "hex.builtin.provider.command.optional": "Optional", "hex.builtin.provider.disk": "Raw Disk", "hex.builtin.provider.disk.disk_size": "Disk Size", "hex.builtin.provider.disk.elevation": "Accessing raw disks most likely requires elevated privileges", diff --git a/plugins/builtin/source/content/providers.cpp b/plugins/builtin/source/content/providers.cpp index 347159f9e..2d2261914 100644 --- a/plugins/builtin/source/content/providers.cpp +++ b/plugins/builtin/source/content/providers.cpp @@ -11,15 +11,16 @@ #include #include #include -#include +#include #include #include #include -#include +#include #include +#include #include namespace hex::plugin::builtin { @@ -31,6 +32,7 @@ namespace hex::plugin::builtin { #if !defined(OS_WEB) ContentRegistry::Provider::add(); ContentRegistry::Provider::add(); + ContentRegistry::Provider::add(); #endif ContentRegistry::Provider::add(); ContentRegistry::Provider::add(); diff --git a/plugins/builtin/source/content/providers/command_provider.cpp b/plugins/builtin/source/content/providers/command_provider.cpp new file mode 100644 index 000000000..33e63ca6e --- /dev/null +++ b/plugins/builtin/source/content/providers/command_provider.cpp @@ -0,0 +1,284 @@ +#if !defined(OS_WEB) + +#include "content/providers/command_provider.hpp" + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace hex::plugin::builtin { + + using namespace std::chrono_literals; + + CommandProvider::CommandProvider() { } + + bool CommandProvider::isAvailable() const { + return m_open; + } + + bool CommandProvider::isReadable() const { + return true; + } + + bool CommandProvider::isWritable() const { + return !m_writeCommand.empty(); + } + + bool CommandProvider::isResizable() const { + return !m_resizeCommand.empty(); + } + + bool CommandProvider::isSavable() const { + return !m_saveCommand.empty(); + } + + static std::vector executeCommand(const std::string &command, std::span stdinData = {}) { + std::vector output; + + #if defined(_WIN32) + + HANDLE hStdinRead = nullptr, hStdinWrite = nullptr; + HANDLE hStdoutRead = nullptr, hStdoutWrite = nullptr; + + SECURITY_ATTRIBUTES sa{}; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + + if (!CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) { + log::error("CreatePipe(stdout) failed"); + return {}; + } + + if (!SetHandleInformation(hStdoutRead, HANDLE_FLAG_INHERIT, 0)) { + log::error("SetHandleInformation(stdout) failed"); + CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite); + return {}; + } + + if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0)) { + log::error("CreatePipe(stdin) failed"); + CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite); + return {}; + } + + if (!SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0)) { + log::error("SetHandleInformation(stdin) failed"); + CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite); + CloseHandle(hStdinRead); CloseHandle(hStdinWrite); + return {}; + } + + STARTUPINFOW si{}; + si.cb = sizeof(si); + si.hStdOutput = si.hStdError = hStdoutWrite; + si.hStdInput = hStdinRead; + si.dwFlags = STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi{}; + + // UTF-16 conversion + auto wcmd = wolv::util::utf8ToWstring(command); + + if (!CreateProcessW( + nullptr, wcmd->data(), + nullptr, nullptr, + TRUE, + 0, + nullptr, nullptr, + &si, &pi + )) { + log::error("CreateProcessW failed"); + CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite); + CloseHandle(hStdinRead); CloseHandle(hStdinWrite); + return {}; + } + + CloseHandle(hStdoutWrite); + CloseHandle(hStdinRead); + + // Write stdin + if (!stdinData.empty()) { + DWORD written = 0; + WriteFile(hStdinWrite, stdinData.data(), + (DWORD)stdinData.size(), &written, nullptr); + } + CloseHandle(hStdinWrite); + + // Read stdout + u8 buffer[4096]; + DWORD bytesRead = 0; + + while (ReadFile(hStdoutRead, buffer, sizeof(buffer), &bytesRead, nullptr) && bytesRead > 0) { + output.insert(output.end(), buffer, buffer + bytesRead); + } + + CloseHandle(hStdoutRead); + + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return output; + + #else + + int stdinPipe[2]; + int stdoutPipe[2]; + + if (pipe(stdinPipe) != 0 || pipe(stdoutPipe) != 0) { + log::error("pipe() failed"); + return {}; + } + + pid_t pid = fork(); + if (pid < 0) { + log::error("fork() failed"); + return {}; + } + + if (pid == 0) { + // Child + dup2(stdinPipe[0], STDIN_FILENO); + dup2(stdoutPipe[1], STDOUT_FILENO); + dup2(stdoutPipe[1], STDERR_FILENO); + + close(stdinPipe[1]); + close(stdoutPipe[0]); + + execl("/bin/sh", "sh", "-c", command.c_str(), (char*)nullptr); + + _exit(127); + } + + // Parent + close(stdinPipe[0]); + close(stdoutPipe[1]); + + if (!stdinData.empty()) { + write(stdinPipe[1], stdinData.data(), stdinData.size()); + } + close(stdinPipe[1]); + + u8 buffer[4096]; + while (true) { + ssize_t n = read(stdoutPipe[0], buffer, sizeof(buffer)); + if (n <= 0) break; + output.insert(output.end(), buffer, buffer + n); + } + + close(stdoutPipe[0]); + waitpid(pid, nullptr, 0); + + return output; + + #endif + } + + static std::string executeCommandString(const std::string &command) { + auto output = executeCommand(command); + return std::string(output.begin(), output.end()); + } + + void CommandProvider::readFromSource(u64 offset, void *buffer, size_t size) { + auto output = executeCommand( + fmt::format(fmt::runtime(m_readCommand), + fmt::arg("address", offset), + fmt::arg("size", size) + ) + ); + + if (output.size() < size) { + std::memcpy(buffer, output.data(), output.size()); + std::memset(static_cast(buffer) + output.size(), 0, size - output.size()); + } else { + std::memcpy(buffer, output.data(), size); + } + } + + void CommandProvider::writeToSource(u64 offset, const void *buffer, size_t size) { + if (m_writeCommand.empty()) + return; + + std::ignore = executeCommand( + fmt::format(fmt::runtime(m_writeCommand), + fmt::arg("address", offset), + fmt::arg("size", size) + ), + { static_cast(buffer), size } + ); + } + + void CommandProvider::save() { + Provider::save(); + std::ignore = executeCommand(m_saveCommand); + } + + u64 CommandProvider::getSourceSize() const { + if (m_sizeCommand.empty()) + return std::numeric_limits::max(); + + const auto output = executeCommandString(m_sizeCommand); + return wolv::util::from_chars(output).value_or(0); + } + + std::string CommandProvider::getName() const { + return fmt::format("hex.builtin.provider.command.name"_lang, m_name); + } + + bool CommandProvider::open() { + m_open = true; + return true; + } + + void CommandProvider::close() { + + } + + bool CommandProvider::drawLoadInterface() { + ImGui::InputText("hex.builtin.provider.command.load.name"_lang, m_name); + ImGui::Separator(); + ImGui::NewLine(); + + ImGui::InputText("hex.builtin.provider.command.load.read_command"_lang, m_readCommand); + ImGui::InputTextWithHint("hex.builtin.provider.command.load.write_command"_lang, "hex.builtin.provider.command.optional"_lang, m_writeCommand); + ImGui::InputTextWithHint("hex.builtin.provider.command.load.size_command"_lang, "hex.builtin.provider.command.optional"_lang, m_sizeCommand); + ImGui::InputTextWithHint("hex.builtin.provider.command.load.resize_command"_lang, "hex.builtin.provider.command.optional"_lang, m_resizeCommand); + ImGui::InputTextWithHint("hex.builtin.provider.command.load.save_command"_lang, "hex.builtin.provider.command.optional"_lang, m_saveCommand); + ImGuiExt::HelpHover("hex.builtin.provider.command.load.hint"_lang, ICON_VS_INFO); + + return !m_name.empty() && !m_readCommand.empty(); + } + + void CommandProvider::loadSettings(const nlohmann::json &settings) { + Provider::loadSettings(settings); + + m_readCommand = settings.value("read", ""); + m_writeCommand = settings.value("write", ""); + m_resizeCommand = settings.value("resize", ""); + m_sizeCommand = settings.value("size", ""); + m_saveCommand = settings.value("save", ""); + m_name = settings.value("name", ""); + } + + nlohmann::json CommandProvider::storeSettings(nlohmann::json settings) const { + settings["read"] = m_readCommand; + settings["write"] = m_writeCommand; + settings["resize"] = m_resizeCommand; + settings["size"] = m_sizeCommand; + settings["save"] = m_saveCommand; + settings["name"] = m_name; + + return Provider::storeSettings(settings); + } + +} + +#endif \ No newline at end of file