From 96b27645d6da509dd556d4b0ac7d38720c530233 Mon Sep 17 00:00:00 2001 From: sonorousfreq Date: Thu, 11 Sep 2025 11:50:38 +0200 Subject: [PATCH] feat: Add region sidebar list to Intel Hex and Motorola SREC providers (#2417) Add block list for intel hex and motorola srec TEST: Load various hex and srec files - Test search capability - Test jump to section ### Problem description The intel-hex/motorola formats can be loaded but not quite ideally, including there is no list of segments/blocks that can be extracted from file layout (metadata). ### Implementation description I implemented similar feature as process monitor provider does, to extract chunks while parsing the file and show them as a sidebar list ### Screenshots Looks like this: image ### Additional things There are some improvements that could be made: 1. There is currently no API to jump to address, which would be useful to jump to beginning of a hex/srec segment. 2. When jumping with setSelection, jumping backwards makes the first visible line the jump address/line, however, when jumping forward, the jump address is at the bottom (see image). 3. Unsure about convention for searches, should we search elements as startsWith given user string, or contains or including 0x prefix or not, whether to keep prefix zeros since the region size is 8 bytes, but addresses should be <= 32 bits, etc. --- .../content/providers/intel_hex_provider.hpp | 22 ++- .../content/providers/intel_hex_provider.cpp | 126 +++++++++++++++--- .../providers/motorola_srec_provider.cpp | 20 +-- 3 files changed, 129 insertions(+), 39 deletions(-) diff --git a/plugins/builtin/include/content/providers/intel_hex_provider.hpp b/plugins/builtin/include/content/providers/intel_hex_provider.hpp index 4fce281da..afbee08bb 100644 --- a/plugins/builtin/include/content/providers/intel_hex_provider.hpp +++ b/plugins/builtin/include/content/providers/intel_hex_provider.hpp @@ -2,14 +2,25 @@ #include #include +#include #include +#include namespace hex::plugin::builtin { + struct MemoryRegion { + Region region; + std::string name; + + constexpr bool operator<(const MemoryRegion &other) const { + return this->region.getStartAddress() < other.region.getStartAddress(); + } + }; class IntelHexProvider : public hex::prv::Provider, public hex::prv::IProviderDataDescription, - public hex::prv::IProviderFilePicker { + public hex::prv::IProviderFilePicker, + public hex::prv::IProviderSidebarInterface { public: IntelHexProvider() = default; ~IntelHexProvider() override = default; @@ -21,11 +32,13 @@ namespace hex::plugin::builtin { [[nodiscard]] bool isSavable() const override { return false; } void setBaseAddress(u64 address) override; + void drawSidebarInterface() override; 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; - + void processMemoryRegions(wolv::util::Expected>, std::string> data); + static bool memoryRegionFilter(const std::string &search, const MemoryRegion &memoryRegion); bool open() override; void close() override; @@ -52,7 +65,8 @@ namespace hex::plugin::builtin { size_t m_dataSize = 0x00; wolv::container::IntervalTree> m_data; + ui::SearchableWidget m_regionSearchWidget = ui::SearchableWidget(memoryRegionFilter); + std::vector m_memoryRegions; std::fs::path m_sourceFilePath; }; - -} \ No newline at end of file +} diff --git a/plugins/builtin/source/content/providers/intel_hex_provider.cpp b/plugins/builtin/source/content/providers/intel_hex_provider.cpp index d92916b49..d7e5dd680 100644 --- a/plugins/builtin/source/content/providers/intel_hex_provider.cpp +++ b/plugins/builtin/source/content/providers/intel_hex_provider.cpp @@ -2,9 +2,13 @@ #include +#include #include -#include #include +#include +#include +#include +#include #include @@ -194,6 +198,53 @@ namespace hex::plugin::builtin { return m_dataSize; } + void IntelHexProvider::processMemoryRegions(wolv::util::Expected>, std::string> data) { + std::optional maxAddress; + bool firstAddress = true; + u64 regionStartAddr = 0; + u32 prevAddrEnd = 0; + u32 blockIdx = 0; + u64 blockSize = 0; + + for (auto &[address, bytes] : data.value()) { + auto endAddress = (address + bytes.size()) - 1; + if (firstAddress) { + regionStartAddr = address; + firstAddress = false; + } else { + if (address > (prevAddrEnd + 1)) { + m_memoryRegions.emplace_back(Region(regionStartAddr, blockSize), fmt::format("Block {}", blockIdx)); + regionStartAddr = address; + blockSize = 0; + blockIdx++; + } + } + blockSize += bytes.size(); + prevAddrEnd = endAddress; + + m_data.emplace({ address, endAddress }, std::move(bytes)); + if (endAddress > maxAddress) + maxAddress = endAddress; + } + + if (blockSize > 0) { + m_memoryRegions.emplace_back(Region(regionStartAddr, blockSize), fmt::format("Block {}", blockIdx)); + } + + if (maxAddress.has_value()) + m_dataSize = *maxAddress + 1; + else + m_dataSize = 0x00; + + m_dataValid = true; + + TaskManager::doLater([this] { + // Jump to first region after loading all regions + auto [region, _] = m_memoryRegions.front(); + ImHexApi::HexEditor::setSelection(region.getStartAddress(), 1); + }); + } + bool IntelHexProvider::open() { auto file = wolv::io::File(m_sourceFilePath, wolv::io::File::Mode::Read); if (!file.isValid()) { @@ -206,22 +257,7 @@ namespace hex::plugin::builtin { this->setErrorMessage(data.error()); return false; } - - std::optional maxAddress; - for (auto &[address, bytes] : data.value()) { - auto endAddress = (address + bytes.size()) - 1; - m_data.emplace({ address, endAddress }, std::move(bytes)); - - if (endAddress > maxAddress) - maxAddress = endAddress; - } - - if (maxAddress.has_value()) - m_dataSize = *maxAddress + 1; - else - m_dataSize = 0x00; - - m_dataValid = true; + processMemoryRegions(data); return true; } @@ -285,6 +321,62 @@ namespace hex::plugin::builtin { return { Region { closestInterval.start, (closestInterval.end - closestInterval.start) + 1}, Provider::getRegionValidity(address).second }; } + bool IntelHexProvider::memoryRegionFilter(const std::string& search, const MemoryRegion& memoryRegion) { + std::string startAddr = fmt::format("{:#x}", memoryRegion.region.getStartAddress()); + std::string endAddr = fmt::format("{:#x}", memoryRegion.region.getEndAddress()); + + return hex::containsIgnoreCase(startAddr, search) || + hex::containsIgnoreCase(endAddr, search); + } + + void IntelHexProvider::drawSidebarInterface() { + ImGuiExt::Header("hex.builtin.provider.process_memory.memory_regions"_lang, true); + + auto availableX = ImGui::GetContentRegionAvail().x; + ImGui::PushItemWidth(availableX); + const auto &filtered = m_regionSearchWidget.draw(m_memoryRegions); + ImGui::PopItemWidth(); + + auto availableY = ImGui::GetContentRegionAvail().y; + if (ImGui::BeginTable("##module_table", 3, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, + ImVec2(availableX, availableY))) { + ImGui::TableSetupColumn("hex.ui.common.region"_lang); + ImGui::TableSetupColumn("hex.ui.common.size"_lang); + ImGui::TableSetupColumn("hex.ui.common.name"_lang); + ImGui::TableSetupScrollFreeze(0, 1); + + ImGui::TableHeadersRow(); + + for (const auto &memoryRegion : filtered) { + ImGui::PushID(&memoryRegion); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGuiExt::TextFormatted("0x{0:08X} - 0x{1:08X}", + memoryRegion->region.getStartAddress(), memoryRegion->region.getEndAddress()); + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(hex::toByteString(memoryRegion->region.getSize()).c_str()); + + + ImGui::TableNextColumn(); + if (ImGui::Selectable(memoryRegion->name.c_str(), + false, + ImGuiSelectableFlags_SpanAllColumns)) { + ImHexApi::HexEditor::setSelection( + memoryRegion->region.getStartAddress(), 1); + } + + ImGui::PopID(); + } + + ImGui::EndTable(); + } + } + void IntelHexProvider::loadSettings(const nlohmann::json &settings) { Provider::loadSettings(settings); diff --git a/plugins/builtin/source/content/providers/motorola_srec_provider.cpp b/plugins/builtin/source/content/providers/motorola_srec_provider.cpp index 7819b8d35..60ada419d 100644 --- a/plugins/builtin/source/content/providers/motorola_srec_provider.cpp +++ b/plugins/builtin/source/content/providers/motorola_srec_provider.cpp @@ -1,9 +1,8 @@ #include "content/providers/motorola_srec_provider.hpp" - #include -#include #include +#include #include #include @@ -183,22 +182,7 @@ namespace hex::plugin::builtin { this->setErrorMessage(data.error()); return false; } - - std::optional maxAddress; - for (auto &[address, bytes] : data.value()) { - auto endAddress = (address + bytes.size()) - 1; - m_data.emplace({ address, endAddress }, std::move(bytes)); - - if (endAddress > maxAddress) - maxAddress = endAddress; - } - - if (maxAddress.has_value()) - m_dataSize = *maxAddress + 1; - else - m_dataSize = 0x00; - - m_dataValid = true; + processMemoryRegions(data); return true; }