feat: Add new Command Line data source

This commit is contained in:
WerWolv 2025-12-07 16:24:36 +01:00
parent 3f9ce561b9
commit 8267aad79e
5 changed files with 353 additions and 2 deletions

View File

@ -68,6 +68,7 @@ add_imhex_plugin(
source/content/providers/base64_provider.cpp source/content/providers/base64_provider.cpp
source/content/providers/view_provider.cpp source/content/providers/view_provider.cpp
source/content/providers/udp_provider.cpp source/content/providers/udp_provider.cpp
source/content/providers/command_provider.cpp
source/content/tools/ascii_table.cpp source/content/tools/ascii_table.cpp
source/content/tools/base_converter.cpp source/content/tools/base_converter.cpp

View File

@ -0,0 +1,54 @@
#pragma once
#include <hex/providers/provider.hpp>
#include <wolv/net/socket_client.hpp>
#include <fonts/vscode_icons.hpp>
#include <hex/providers/cached_provider.hpp>
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;
};
}

View File

@ -420,6 +420,16 @@
"hex.builtin.provider.tooltip.show_more": "Hold SHIFT for more information", "hex.builtin.provider.tooltip.show_more": "Hold SHIFT for more information",
"hex.builtin.provider.error.open": "Failed to open data provider: {}", "hex.builtin.provider.error.open": "Failed to open data provider: {}",
"hex.builtin.provider.base64": "Base64 File", "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": "Raw Disk",
"hex.builtin.provider.disk.disk_size": "Disk Size", "hex.builtin.provider.disk.disk_size": "Disk Size",
"hex.builtin.provider.disk.elevation": "Accessing raw disks most likely requires elevated privileges", "hex.builtin.provider.disk.elevation": "Accessing raw disks most likely requires elevated privileges",

View File

@ -11,15 +11,16 @@
#include <content/providers/process_memory_provider.hpp> #include <content/providers/process_memory_provider.hpp>
#include <content/providers/base64_provider.hpp> #include <content/providers/base64_provider.hpp>
#include <content/providers/udp_provider.hpp> #include <content/providers/udp_provider.hpp>
#include <popups/popup_notification.hpp> #include <content/providers/command_provider.hpp>
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/task_manager.hpp> #include <hex/api/task_manager.hpp>
#include <hex/helpers/fmt.hpp> #include <hex/helpers/fmt.hpp>
#include <nlohmann/json.hpp> #include <popups/popup_notification.hpp>
#include <toasts/toast_notification.hpp> #include <toasts/toast_notification.hpp>
#include <nlohmann/json.hpp>
#include <wolv/utils/guards.hpp> #include <wolv/utils/guards.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -31,6 +32,7 @@ namespace hex::plugin::builtin {
#if !defined(OS_WEB) #if !defined(OS_WEB)
ContentRegistry::Provider::add<DiskProvider>(); ContentRegistry::Provider::add<DiskProvider>();
ContentRegistry::Provider::add<UDPProvider>(); ContentRegistry::Provider::add<UDPProvider>();
ContentRegistry::Provider::add<CommandProvider>();
#endif #endif
ContentRegistry::Provider::add<GDBProvider>(); ContentRegistry::Provider::add<GDBProvider>();
ContentRegistry::Provider::add<IntelHexProvider>(); ContentRegistry::Provider::add<IntelHexProvider>();

View File

@ -0,0 +1,284 @@
#if !defined(OS_WEB)
#include "content/providers/command_provider.hpp"
#include <chrono>
#include <imgui.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <hex/helpers/fmt.hpp>
#include <hex/api/localization_manager.hpp>
#include <hex/helpers/logger.hpp>
#include <nlohmann/json.hpp>
#include <wolv/utils/charconv.hpp>
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<u8> executeCommand(const std::string &command, std::span<const u8> stdinData = {}) {
std::vector<u8> 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<u8*>(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<const u8*>(buffer), size }
);
}
void CommandProvider::save() {
Provider::save();
std::ignore = executeCommand(m_saveCommand);
}
u64 CommandProvider::getSourceSize() const {
if (m_sizeCommand.empty())
return std::numeric_limits<u32>::max();
const auto output = executeCommandString(m_sizeCommand);
return wolv::util::from_chars<u64>(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