diff --git a/plugins/builtin/include/content/helpers/diagrams.hpp b/plugins/builtin/include/content/helpers/diagrams.hpp index 7c0ce083e..dc8947764 100644 --- a/plugins/builtin/include/content/helpers/diagrams.hpp +++ b/plugins/builtin/include/content/helpers/diagrams.hpp @@ -10,20 +10,33 @@ #include #include +#include #include #include #include -#include #include -#include +#include namespace hex { namespace impl { + struct AnnotationRegion { + UnlocalizedString unlocalizedName; + Region region; + ImColor color; + }; + + struct Tag { + UnlocalizedString unlocalizedName; + ImU64 value; + ImAxis axis; + ImGuiCol color; + }; + inline int IntegerAxisFormatter(double value, char* buffer, int size, void *userData) { u64 integer = static_cast(value); return snprintf(buffer, size, static_cast(userData), integer); @@ -380,6 +393,39 @@ namespace hex { // Draw the plot ImPlot::PlotLine("##ChunkBasedAnalysisLine", m_xBlockEntropy.data(), m_yBlockEntropySampled.data(), m_xBlockEntropy.size()); + if (m_showAnnotations) { + u32 id = 1; + for (const auto &annotation : m_annotationRegions) { + const auto ®ion = annotation.region; + double xMin = region.getStartAddress(); + double xMax = region.getEndAddress(); + double yMin = 0.0F; + double yMax = 100.0F; + + ImPlot::DragRect(id, &xMin, &yMin, &xMax, &yMax, annotation.color, ImPlotDragToolFlags_NoFit | ImPlotDragToolFlags_NoInputs); + + const auto min = ImFloor(ImPlot::PlotToPixels(xMin, yMax)); + const auto max = ImFloor(ImPlot::PlotToPixels(xMax, yMin)) + scaled({ 1, 1 }); + const auto mousePos = ImPlot::PixelsToPlot(ImGui::GetMousePos()); + if (ImGui::IsMouseHoveringRect(min, max)) { + ImPlot::Annotation(xMin + (xMax - xMin) / 2, mousePos.y, annotation.color, ImVec2(), false, "%s", Lang(annotation.unlocalizedName).get()); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + ImHexApi::HexEditor::setSelection(annotation.region); + } + } + + id += 1; + } + + for (const auto &tag : m_tags) { + if (tag.axis == ImAxis_X1) + ImPlot::TagX(tag.value, ImGui::GetStyleColorVec4(tag.color), "%s", Lang(tag.unlocalizedName).get()); + else if (tag.axis == ImAxis_Y1) + ImPlot::TagY(tag.value, ImGui::GetStyleColorVec4(tag.color), "%s", Lang(tag.unlocalizedName).get()); + } + } + // The parameter updateHandle is used when using the pattern language since we don't have a provider // but just a set of bytes, we won't be able to use the drag bar correctly. if (updateHandle) { @@ -563,6 +609,10 @@ namespace hex { m_handlePosition = filePosition; } + void enableAnnotations(bool enabled) { + m_showAnnotations = enabled; + } + private: // Private method used to factorize the process public method void processImpl(const std::vector &bytes) { @@ -610,6 +660,41 @@ namespace hex { for (u64 i = 0; i < m_blockCount; ++i) m_xBlockEntropy[i] = ((m_startAddress / m_blockSize) + stride * i) * m_blockSize; m_xBlockEntropy.push_back(m_endAddress); + + u64 index = 0; + for (const auto [first, second] : std::views::adjacent<2>(m_yBlockEntropySampled)) { + const auto relativeDifference = std::abs(first - second) / std::max(first, second); + + if (relativeDifference > 0.5 && index + 1 < m_xBlockEntropy.size()) { + const u64 start = u64(m_xBlockEntropy[index + 1]); + const u64 size = u64(m_xBlockEntropy[index + 1] - m_xBlockEntropy[index + 0]); + Region region = { start, size }; + if (first > second) { + this->addRegion("hex.ui.diagram.entropy_analysis.entropy_drop", region, 0x60FF2020); + } else { + this->addRegion("hex.ui.diagram.entropy_analysis.entropy_spike", region, 0x602020FF); + } + } + + index += 1; + } + } + + void addRegion(const UnlocalizedString &unlocalizedName, Region region, ImColor color) { + const auto existingRegion = std::ranges::find_if(m_annotationRegions, [this, ®ion](const impl::AnnotationRegion &annotation) { + auto difference = i64(region.getEndAddress()) - i64(annotation.region.getEndAddress()); + return difference > 0 && difference < i64(m_blockSize * 32); + }); + + if (existingRegion != m_annotationRegions.end()) { + existingRegion->region.size += region.size; + } else { + m_annotationRegions.push_back({ unlocalizedName, region, color }); + } + } + + void addTag(const UnlocalizedString &name, u64 value, ImAxis axis, ImGuiCol color) { + m_tags.push_back({ name, value, axis, color }); } private: @@ -650,6 +735,11 @@ namespace hex { size_t m_sampleSize = 0; std::atomic m_processing = false; + + std::vector m_annotationRegions; + std::vector m_tags; + + bool m_showAnnotations = true; }; class DiagramByteDistribution { @@ -748,20 +838,6 @@ namespace hex { }; class DiagramByteTypesDistribution { - private: - struct AnnotationRegion { - UnlocalizedString unlocalizedName; - Region region; - ImColor color; - }; - - struct Tag { - UnlocalizedString unlocalizedName; - ImU64 value; - ImAxis axis; - ImGuiCol color; - }; - public: explicit DiagramByteTypesDistribution(u64 blockSize = 256, size_t sampleSize = 0x1000) : m_blockSize(blockSize), m_sampleSize(sampleSize){ } @@ -1049,8 +1125,8 @@ namespace hex { m_xBlockTypeDistributions.push_back(m_endAddress); } - void addRegion(const UnlocalizedString &name, Region region, ImColor color) { - const auto existingRegion = std::ranges::find_if(m_annotationRegions, [this, ®ion](const AnnotationRegion &annotation) { + void addRegion(const UnlocalizedString &unlocalizedName, Region region, ImColor color) { + const auto existingRegion = std::ranges::find_if(m_annotationRegions, [this, ®ion](const impl::AnnotationRegion &annotation) { auto difference = i64(region.getEndAddress()) - i64(annotation.region.getEndAddress()); return difference > 0 && difference < i64(m_blockSize * 32); }); @@ -1058,7 +1134,7 @@ namespace hex { if (existingRegion != m_annotationRegions.end()) { existingRegion->region.size += region.size; } else { - m_annotationRegions.push_back({ name, region, color }); + m_annotationRegions.push_back({ unlocalizedName, region, color }); } } @@ -1103,8 +1179,8 @@ namespace hex { std::array, 12> m_yBlockTypeDistributions, m_yBlockTypeDistributionsSampled; std::atomic m_processing = false; - std::vector m_annotationRegions; - std::vector m_tags; + std::vector m_annotationRegions; + std::vector m_tags; bool m_showAnnotations = true; }; diff --git a/plugins/builtin/source/content/data_information_sections.cpp b/plugins/builtin/source/content/data_information_sections.cpp index 885e3fcd2..734210d73 100644 --- a/plugins/builtin/source/content/data_information_sections.cpp +++ b/plugins/builtin/source/content/data_information_sections.cpp @@ -180,6 +180,7 @@ namespace hex::plugin::builtin { m_chunkBasedEntropy.reset(m_inputChunkSize, region.getStartAddress(), region.getEndAddress(), provider->getBaseAddress(), provider->getActualSize()); + m_chunkBasedEntropy.enableAnnotations(m_showAnnotations); m_byteTypesDistribution.enableAnnotations(m_showAnnotations); // Create a handle to the file diff --git a/plugins/ui/romfs/lang/en_US.json b/plugins/ui/romfs/lang/en_US.json index 88fe434ab..defc42c0c 100644 --- a/plugins/ui/romfs/lang/en_US.json +++ b/plugins/ui/romfs/lang/en_US.json @@ -122,6 +122,8 @@ "hex.ui.pattern_drawer.visualizer.unknown": "Unknown visualizer", "hex.ui.pattern_drawer.visualizer.invalid_parameter_count": "Invalid parameter count", "hex.ui.diagram.byte_type_distribution.plain_text": "Plain Text", - "hex.ui.diagram.byte_type_distribution.similar_bytes": "Similar Bytes" + "hex.ui.diagram.byte_type_distribution.similar_bytes": "Similar Bytes", + "hex.ui.diagram.entropy_analysis.entropy_drop": "Large Entropy Drop", + "hex.ui.diagram.entropy_analysis.entropy_spike": "Large Entropy Spike" } }