From bf08ed563a1371ae3624870f872ef00d5908bf20 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 28 Aug 2025 23:22:26 +0200 Subject: [PATCH] feat: Added Hex Editor "Decode as Encoding" option --- .../include/hex/helpers/encoding_file.hpp | 3 +- lib/libimhex/source/helpers/encoding_file.cpp | 15 +++- .../include/content/views/view_hex_editor.hpp | 3 + plugins/builtin/romfs/lang/en_US.json | 2 + .../builtin/source/content/data_inspector.cpp | 9 +-- .../source/content/views/view_hex_editor.cpp | 69 ++++++++++++++++++- 6 files changed, 90 insertions(+), 11 deletions(-) diff --git a/lib/libimhex/include/hex/helpers/encoding_file.hpp b/lib/libimhex/include/hex/helpers/encoding_file.hpp index 71c93a652..6673497d3 100644 --- a/lib/libimhex/include/hex/helpers/encoding_file.hpp +++ b/lib/libimhex/include/hex/helpers/encoding_file.hpp @@ -27,10 +27,11 @@ namespace hex { EncodingFile& operator=(const EncodingFile &other); EncodingFile& operator=(EncodingFile &&other) noexcept; - [[nodiscard]] std::pair getEncodingFor(std::span buffer) const; + [[nodiscard]] std::pair getEncodingFor(std::span buffer) const; [[nodiscard]] u64 getEncodingLengthFor(std::span buffer) const; [[nodiscard]] u64 getShortestSequence() const { return m_shortestSequence; } [[nodiscard]] u64 getLongestSequence() const { return m_longestSequence; } + [[nodiscard]] std::string decodeAll(std::span buffer) const; [[nodiscard]] bool valid() const { return m_valid; } diff --git a/lib/libimhex/source/helpers/encoding_file.cpp b/lib/libimhex/source/helpers/encoding_file.cpp index f597062a9..8909d5e0e 100644 --- a/lib/libimhex/source/helpers/encoding_file.cpp +++ b/lib/libimhex/source/helpers/encoding_file.cpp @@ -88,7 +88,7 @@ namespace hex { - std::pair EncodingFile::getEncodingFor(std::span buffer) const { + std::pair EncodingFile::getEncodingFor(std::span buffer) const { for (auto riter = m_mapping->crbegin(); riter != m_mapping->crend(); ++riter) { const auto &[size, mapping] = *riter; @@ -116,6 +116,19 @@ namespace hex { return 1; } + std::string EncodingFile::decodeAll(std::span buffer) const { + std::string result; + + while (!buffer.empty()) { + const auto [character, size] = getEncodingFor(buffer); + result += character; + buffer = buffer.subspan(size); + } + + return result; + } + + void EncodingFile::parse(const std::string &content) { m_tableContent = content; for (const auto &line : wolv::util::splitString(m_tableContent, "\n")) { diff --git a/plugins/builtin/include/content/views/view_hex_editor.hpp b/plugins/builtin/include/content/views/view_hex_editor.hpp index dbc0e7aaf..4467bd7e2 100644 --- a/plugins/builtin/include/content/views/view_hex_editor.hpp +++ b/plugins/builtin/include/content/views/view_hex_editor.hpp @@ -26,6 +26,9 @@ namespace hex::plugin::builtin { [[nodiscard]] virtual bool canBePinned() const { return false; } [[nodiscard]] bool isPinned() const { return m_isPinned; } void setPinned(const bool pinned) { m_isPinned = pinned; } + + [[nodiscard]] virtual ImGuiWindowFlags getFlags() const { return ImGuiWindowFlags_AlwaysAutoResize; } + private: bool m_isPinned = false; }; diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 4ab276282..f345f9ca9 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -877,6 +877,8 @@ "hex.builtin.view.hex_editor.menu.edit.select_all": "Select all", "hex.builtin.view.hex_editor.menu.edit.set_base": "Set Base Address...", "hex.builtin.view.hex_editor.menu.edit.set_page_size": "Set Page Size...", + "hex.builtin.view.hex_editor.menu.edit.decode_as_text": "Decode as Encoding", + "hex.builtin.view.hex_editor.menu.edit.decoded_string.popup.title": "Decoded Text", "hex.builtin.view.hex_editor.menu.file.goto": "Go to address...", "hex.builtin.view.hex_editor.menu.file.skip_until": "Skip Until", "hex.builtin.view.hex_editor.menu.file.skip_until.previous_differing_byte": "Previous Differing Byte", diff --git a/plugins/builtin/source/content/data_inspector.cpp b/plugins/builtin/source/content/data_inspector.cpp index 018e1dc4f..d75ba7d62 100644 --- a/plugins/builtin/source/content/data_inspector.cpp +++ b/plugins/builtin/source/content/data_inspector.cpp @@ -689,14 +689,7 @@ namespace hex::plugin::builtin { std::vector stringBuffer(std::min(currSelection->size, 0x1000), 0x00); ImHexApi::Provider::get()->read(currSelection->address, stringBuffer.data(), stringBuffer.size()); - u64 offset = 0; - while (offset < stringBuffer.size()) { - const auto [character, size] = encodingFile.getEncodingFor(std::span(stringBuffer).subspan(offset)); - value += character; - offset += size; - } - copyValue = value; - + copyValue = value = encodingFile.decodeAll(stringBuffer); if (value.size() > MaxStringLength) { value.resize(MaxStringLength); diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 9d5092962..47939dfc2 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include using namespace std::literals::string_literals; @@ -545,6 +546,46 @@ namespace hex::plugin::builtin { std::function m_pasteCallback; }; + class PopupDecodedString final : public ViewHexEditor::Popup { + public: + explicit PopupDecodedString(std::string decodedString) : m_decodedString(std::move(decodedString)) { + } + + void draw(ViewHexEditor *) override { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2()); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4()); + + auto text = wolv::util::trim(wolv::util::wrapMonospacedString( + m_decodedString, + ImGui::CalcTextSize("M").x, + std::max(100_scaled, ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ScrollbarSize - ImGui::GetStyle().FrameBorderSize) + )); + + ImGui::InputTextMultiline( + "##", + text.data(), + text.size() + 1, + ImGui::GetContentRegionAvail(), + ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_NoHorizontalScroll + ); + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); + } + + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.decoded_string.popup.title"; + } + + [[nodiscard]] bool canBePinned() const override { return true; } + [[nodiscard]] ImGuiWindowFlags getFlags() const override { return ImGuiWindowFlags_None; } + + private: + std::string m_decodedString; + ui::TextEditor m_editor; + }; + /* Hex Editor */ ViewHexEditor::ViewHexEditor() : View::Window("hex.builtin.view.hex_editor.name", ICON_VS_FILE_BINARY) { @@ -711,7 +752,7 @@ namespace hex::plugin::builtin { } if (m_currPopup != nullptr) { - if (ImGui::Begin(fmt::format("##{}", m_currPopup->getTitle().get()).c_str(), &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) { + if (ImGui::Begin(fmt::format("##{}", m_currPopup->getTitle().get()).c_str(), &open, m_currPopup->getFlags() | ImGuiWindowFlags_NoDocking)) { if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { this->closePopup(); } else { @@ -1666,6 +1707,32 @@ namespace hex::plugin::builtin { }, [] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid(); }, this); + + /* Decode as Text */ + ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.decode_as_text" }, ICON_VS_CHAT_SPARKLE, 1960, Shortcut::None, + [this] { + const auto selection = ImHexApi::HexEditor::getSelection(); + + TaskManager::createTask("", TaskManager::NoProgress, [this, selection] { + const auto &customEncoding = this->m_hexEditor.getCustomEncoding(); + if (!customEncoding.has_value()) + return; + + std::vector buffer(selection->getSize()); + selection->getProvider()->read(selection->getStartAddress(), buffer.data(), buffer.size()); + + auto decodedString = customEncoding->decodeAll(buffer); + TaskManager::doLater([this, decodedString = std::move(decodedString)]() mutable { + this->openPopup(std::move(decodedString)); + }); + }); + }, + [this] { + return ImHexApi::HexEditor::isSelectionValid() && + ImHexApi::Provider::isValid() && + this->m_hexEditor.getCustomEncoding().has_value(); + }, + this); } }