From 03884ddd057adef8aeb34fab5ddf0cc88cc7be93 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 29 May 2025 18:00:29 +0200 Subject: [PATCH] feat: Added simple UDP Data Provider --- lib/libimhex/CMakeLists.txt | 1 + .../include/hex/helpers/udp_server.hpp | 58 +++++++++ lib/libimhex/source/helpers/udp_server.cpp | 69 ++++++++++ plugins/builtin/CMakeLists.txt | 1 + .../content/providers/udp_provider.hpp | 62 +++++++++ plugins/builtin/romfs/lang/en_US.json | 4 + plugins/builtin/source/content/providers.cpp | 2 + .../source/content/providers/udp_provider.cpp | 122 ++++++++++++++++++ 8 files changed, 319 insertions(+) create mode 100644 lib/libimhex/include/hex/helpers/udp_server.hpp create mode 100644 lib/libimhex/source/helpers/udp_server.cpp create mode 100644 plugins/builtin/include/content/providers/udp_provider.hpp create mode 100644 plugins/builtin/source/content/providers/udp_provider.cpp diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index ec00836c6..0168a9d79 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -41,6 +41,7 @@ set(LIBIMHEX_SOURCES source/helpers/semantic_version.cpp source/helpers/keys.cpp source/helpers/freetype.cpp + source/helpers/udp_server.cpp source/test/tests.cpp diff --git a/lib/libimhex/include/hex/helpers/udp_server.hpp b/lib/libimhex/include/hex/helpers/udp_server.hpp new file mode 100644 index 000000000..f06d7a9a2 --- /dev/null +++ b/lib/libimhex/include/hex/helpers/udp_server.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include +#include +#include + +namespace hex { + + class UDPServer { + public: + using Callback = std::function data)>; + UDPServer() = default; + UDPServer(u16 port, Callback callback); + ~UDPServer(); + + UDPServer(const UDPServer&) = delete; + UDPServer& operator=(const UDPServer&) = delete; + UDPServer(UDPServer &&other) noexcept { + m_port = other.m_port; + m_callback = std::move(other.m_callback); + m_thread = std::move(other.m_thread); + m_running = other.m_running.load(); + other.m_running = false; + m_socketFd = other.m_socketFd; + other.m_socketFd = -1; + } + UDPServer& operator=(UDPServer &&other) noexcept { + if (this != &other) { + m_port = other.m_port; + m_callback = std::move(other.m_callback); + m_thread = std::move(other.m_thread); + m_running = other.m_running.load(); + other.m_running = false; + m_socketFd = other.m_socketFd; + other.m_socketFd = -1; + } + + return *this; + } + + void start(); + void stop(); + + [[nodiscard]] u16 getPort() const { return m_port; } + + private: + void run(); + + u16 m_port; + Callback m_callback; + std::thread m_thread; + std::atomic m_running; + int m_socketFd; + }; + +} \ No newline at end of file diff --git a/lib/libimhex/source/helpers/udp_server.cpp b/lib/libimhex/source/helpers/udp_server.cpp new file mode 100644 index 000000000..23eb47368 --- /dev/null +++ b/lib/libimhex/source/helpers/udp_server.cpp @@ -0,0 +1,69 @@ +#include + +#if defined(OS_WINDOWS) + #include + #include + using socklen_t = int; +#else + #include + #include + #include + #include +#endif + +namespace hex { + + UDPServer::UDPServer(u16 port, Callback callback) + : m_port(port), m_callback(std::move(callback)), m_running(false), m_socketFd(-1) { + } + + UDPServer::~UDPServer() { + stop(); + } + + void UDPServer::start() { + m_running = true; + m_thread = std::thread(&UDPServer::run, this); + } + + void UDPServer::stop() { + m_running = false; + if (m_thread.joinable()) m_thread.join(); + + if (m_socketFd >= 0) { + #if defined(OS_WINDOWS) + ::closesocket(m_socketFd); + #else + ::close(sockfd_); + #endif + } + } + + void UDPServer::run() { + m_socketFd = ::socket(AF_INET, SOCK_DGRAM, 0); + if (m_socketFd < 0) { + return; + } + + sockaddr_in addr = { }; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(m_port); + + if (bind(m_socketFd, reinterpret_cast(&addr), sizeof(addr)) < 0) { + return; + } + + std::vector buffer(64 * 1024, 0x00); + while (m_running) { + sockaddr_in client{}; + socklen_t len = sizeof(client); + const auto bytes = ::recvfrom(m_socketFd, reinterpret_cast(buffer.data()), buffer.size(), 0, reinterpret_cast(&client), &len); + + if (bytes > 0) { + buffer[bytes] = '\0'; + m_callback({ buffer.data(), buffer.data() + bytes }); + } + } + } +} \ No newline at end of file diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index f12797a46..a4f1c877a 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -68,6 +68,7 @@ add_imhex_plugin( source/content/providers/process_memory_provider.cpp source/content/providers/base64_provider.cpp source/content/providers/view_provider.cpp + source/content/providers/udp_provider.cpp source/content/tools/ascii_table.cpp source/content/tools/base_converter.cpp diff --git a/plugins/builtin/include/content/providers/udp_provider.hpp b/plugins/builtin/include/content/providers/udp_provider.hpp new file mode 100644 index 000000000..a2348f02f --- /dev/null +++ b/plugins/builtin/include/content/providers/udp_provider.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace hex::plugin::builtin { + + class UDPProvider : public hex::prv::Provider { + public: + UDPProvider() = default; + ~UDPProvider() override = default; + + [[nodiscard]] bool isAvailable() const override { return true; } + [[nodiscard]] bool isReadable() const override { return true; } + [[nodiscard]] bool isWritable() const override { return false; } + [[nodiscard]] bool isResizable() const override { return false; } + [[nodiscard]] bool isSavable() const override { return true; } + + void readRaw(u64 offset, void *buffer, size_t size) override; + void writeRaw(u64 offset, const void *buffer, size_t size) override; + + [[nodiscard]] u64 getActualSize() const override; + + + [[nodiscard]] bool hasLoadInterface() const override { return true; } + [[nodiscard]] bool drawLoadInterface() override; + bool hasInterface() const override { return true; } + void drawInterface() override; + + [[nodiscard]] bool open() override; + void close() override; + + void loadSettings(const nlohmann::json &) override; + [[nodiscard]] nlohmann::json storeSettings(nlohmann::json) const override; + + [[nodiscard]] UnlocalizedString getTypeName() const override { + return "hex.builtin.provider.udp"; + } + + std::string getName() const override { return hex::format("hex.builtin.provider.udp.name"_lang, m_udpServer.getPort()); } + + protected: + void receive(std::span data); + + private: + UDPServer m_udpServer; + int m_port = 0; + + struct Message { + std::vector data; + std::chrono::system_clock::time_point timestamp; + }; + + mutable std::mutex m_mutex; + std::vector m_messages; + u64 m_selectedMessage = 0; + }; + +} diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 74038cca1..1fdb0460e 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -462,6 +462,10 @@ "hex.builtin.provider.process_memory.utils.inject_dll": "Inject DLL", "hex.builtin.provider.process_memory.utils.inject_dll.success": "Successfully injected DLL '{0}'!", "hex.builtin.provider.process_memory.utils.inject_dll.failure": "Failed to inject DLL '{0}'!", + "hex.builtin.provider.udp": "UDP Server", + "hex.builtin.provider.udp.name": "UDP Server on Port {}", + "hex.builtin.provider.udp.port": "Server Port", + "hex.builtin.provider.udp.timestamp": "Timestamp", "hex.builtin.provider.view": "View", "hex.builtin.setting.experiments": "Experiments", "hex.builtin.setting.experiments.description": "Experiments are features that are still in development and may not work correctly yet.\n\nFeel free to try them out and report any issues you encounter!", diff --git a/plugins/builtin/source/content/providers.cpp b/plugins/builtin/source/content/providers.cpp index e71a8d355..fa6e0a2fc 100644 --- a/plugins/builtin/source/content/providers.cpp +++ b/plugins/builtin/source/content/providers.cpp @@ -10,6 +10,7 @@ #include "content/providers/view_provider.hpp" #include #include +#include #include #include @@ -29,6 +30,7 @@ namespace hex::plugin::builtin { ContentRegistry::Provider::add(false); #if !defined(OS_WEB) ContentRegistry::Provider::add(); + ContentRegistry::Provider::add(); #endif ContentRegistry::Provider::add(); ContentRegistry::Provider::add(); diff --git a/plugins/builtin/source/content/providers/udp_provider.cpp b/plugins/builtin/source/content/providers/udp_provider.cpp new file mode 100644 index 000000000..498016071 --- /dev/null +++ b/plugins/builtin/source/content/providers/udp_provider.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +namespace hex::plugin::builtin { + + bool UDPProvider::open() { + m_udpServer = UDPServer(m_port, [this](std::span data) { + this->receive(data); + }); + m_udpServer.start(); + + return true; + } + + void UDPProvider::close() { + m_udpServer.stop(); + } + + void UDPProvider::receive(std::span data) { + std::scoped_lock lock(m_mutex); + + m_messages.emplace_back( + std::vector(data.begin(), data.end()), + std::chrono::system_clock::now() + ); + } + + u64 UDPProvider::getActualSize() const { + std::scoped_lock lock(m_mutex); + + if (m_messages.empty()) + return 0; + + if (m_selectedMessage >= m_messages.size()) + return 0; + + return m_messages[m_selectedMessage].data.size(); + } + + void UDPProvider::readRaw(u64 offset, void* buffer, size_t size) { + std::scoped_lock lock(m_mutex); + + if (m_selectedMessage >= m_messages.size()) + return; + + const auto &message = m_messages[m_selectedMessage]; + + if (offset >= message.data.size()) + return; + + if (offset + size > message.data.size()) { + if (offset >= message.data.size()) + size = offset - message.data.size(); + else + return; + } + + std::memcpy(buffer, &message.data[offset], size); + } + + void UDPProvider::writeRaw(u64, const void*, size_t) { + /* Not supported */ + } + + void UDPProvider::drawInterface() { + std::scoped_lock lock(m_mutex); + + if (ImGui::BeginTable("##Messages", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImGui::GetContentRegionAvail())) { + ImGui::TableSetupColumn("hex.builtin.provider.udp.timestamp"_lang, ImGuiTableColumnFlags_WidthFixed, 32 * ImGui::CalcTextSize(" ").x); + ImGui::TableSetupColumn("hex.ui.common.size"_lang, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + ImGuiListClipper clipper; + clipper.Begin(m_messages.size()); + while (clipper.Step()) + for (u64 i = clipper.DisplayStart; i != u64(clipper.DisplayEnd); i += 1) { + ImGui::PushID(i + 1); + const auto &message = m_messages[i]; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGuiExt::TextFormatted("{}", message.timestamp); + ImGui::SameLine(); + if (ImGui::Selectable("##selectable", i == m_selectedMessage, ImGuiSelectableFlags_SpanAllColumns)) + m_selectedMessage = i; + + ImGui::TableNextColumn(); + ImGuiExt::TextFormatted("{}", hex::toByteString(message.data.size())); + + ImGui::PopID(); + } + + ImGui::EndTable(); + } + } + + bool UDPProvider::drawLoadInterface() { + ImGui::InputInt("hex.builtin.provider.udp.port"_lang, &m_port, 0, 0); + + if (m_port < 0) + m_port = 0; + else if (m_port > 0xFFFF) + m_port = 0xFFFF; + + return m_port != 0; + } + + + void UDPProvider::loadSettings(const nlohmann::json &settings) { + Provider::loadSettings(settings); + + m_port = settings.at("port").get(); + } + + nlohmann::json UDPProvider::storeSettings(nlohmann::json settings) const { + settings["port"] = m_port; + + return Provider::storeSettings(settings); + } + +}