From ed9e463550d58eb6c306aacc4f7035fb8afe2896 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 21 Sep 2021 19:54:13 +0200 Subject: [PATCH] ui: Added diff view --- CMakeLists.txt | 1 + include/views/view_diff.hpp | 15 +- plugins/libimhex/include/hex/views/view.hpp | 2 +- source/init/tasks.cpp | 2 + source/views/view_diff.cpp | 226 +++++++++++++++++++- 5 files changed, 233 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a428ed79c..65479ea54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ add_executable(imhex ${application_type} source/views/view_yara.cpp source/views/view_constants.cpp source/views/view_store.cpp + source/views/view_diff.cpp ${imhex_icon} ) diff --git a/include/views/view_diff.hpp b/include/views/view_diff.hpp index c202c3ac9..f437f2c47 100644 --- a/include/views/view_diff.hpp +++ b/include/views/view_diff.hpp @@ -7,19 +7,28 @@ #include #include +#include namespace hex { namespace prv { class Provider; } - class ViewTools : public View { + class ViewDiff : public View { public: - ViewTools(); - ~ViewTools() override; + ViewDiff(); + ~ViewDiff() override; void drawContent() override; void drawMenu() override; + private: + void drawDiffLine(const std::array &providerIds, u64 row) const; + + int m_providerA = -1, m_providerB = -1; + + bool m_greyedOutZeros = true; + bool m_upperCaseHex = true; + int m_columnCount = 16; }; } \ No newline at end of file diff --git a/plugins/libimhex/include/hex/views/view.hpp b/plugins/libimhex/include/hex/views/view.hpp index b5582910c..f875a6eb4 100644 --- a/plugins/libimhex/include/hex/views/view.hpp +++ b/plugins/libimhex/include/hex/views/view.hpp @@ -51,7 +51,7 @@ namespace hex { bool& getWindowOpenState(); - const std::string& getUnlocalizedName() const; + [[nodiscard]] const std::string& getUnlocalizedName() const; protected: void discardNavigationRequests(); diff --git a/source/init/tasks.cpp b/source/init/tasks.cpp index cfca2e3a9..93b23fe0f 100644 --- a/source/init/tasks.cpp +++ b/source/init/tasks.cpp @@ -29,6 +29,7 @@ #include "views/view_yara.hpp" #include "views/view_constants.hpp" #include "views/view_store.hpp" +#include "views/view_diff.hpp" #include "helpers/plugin_manager.hpp" @@ -191,6 +192,7 @@ namespace hex::init { ContentRegistry::Views::add(); ContentRegistry::Views::add(); ContentRegistry::Views::add(); + ContentRegistry::Views::add(); return true; } diff --git a/source/views/view_diff.cpp b/source/views/view_diff.cpp index e62129b55..e5ac77bc5 100644 --- a/source/views/view_diff.cpp +++ b/source/views/view_diff.cpp @@ -1,25 +1,233 @@ -#include "views/view_tools.hpp" +#include "views/view_diff.hpp" +#include #include +#include + +#include +#include + namespace hex { - ViewTools::ViewTools() : View("hex.view.tools.name") { } + ViewDiff::ViewDiff() : View("hex.view.diff.name") { - ViewTools::~ViewTools() { } + EventManager::subscribe(this, [this]{ + { + auto columnCount = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.column_count"); - void ViewTools::drawContent() { - if (ImGui::Begin(View::toWindowName("hex.view.tools.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { - for (const auto& [name, function] : ContentRegistry::Tools::getEntries()) { - if (ImGui::CollapsingHeader(LangEntry(name))) { - function(); + this->m_columnCount = static_cast(columnCount); + } + + { + auto greyOutZeros = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.grey_zeros"); + + this->m_greyedOutZeros = static_cast(greyOutZeros); + } + + { + auto upperCaseHex = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.uppercase_hex"); + + this->m_upperCaseHex = static_cast(upperCaseHex); + } + }); + + } + + ViewDiff::~ViewDiff() { + EventManager::unsubscribe(this); + } + + static void drawProviderSelector(int &provider) { + auto &providers = ImHexApi::Provider::getProviders(); + + std::string preview; + if (ImHexApi::Provider::isValid() && provider >= 0) + preview = providers[provider]->getName(); + + ImGui::SetNextItemWidth(200 * SharedData::globalScale); + if (ImGui::BeginCombo("", preview.c_str())) { + + for (int i = 0; i < providers.size(); i++) { + if (ImGui::Selectable(providers[i]->getName().c_str())) { + provider = i; } } + + ImGui::EndCombo(); + } + } + + static u32 getDiffColor(u32 color) { + return (color & 0x00FFFFFF) | 0x40000000; + } + + enum class DiffResult { Same, Changed, Added, Removed }; + struct LineInfo { + std::vector bytes; + s64 validBytes = 0; + }; + + static DiffResult diffBytes(u8 index, const LineInfo &a, const LineInfo &b) { + /* Very simple binary diff. Only detects additions and changes */ + if (a.validBytes > index) { + if (b.validBytes <= index) + return DiffResult::Added; + else if (a.bytes[index] != b.bytes[index]) + return DiffResult::Changed; + } + + return DiffResult::Same; + } + + void ViewDiff::drawDiffLine(const std::array &providerIds, u64 row) const { + + std::array lineInfo; + + u8 addressDigitCount = 0; + for (u8 i = 0; i < 2; i++) { + int id = providerIds[i]; + if (id < 0) continue; + + auto &provider = ImHexApi::Provider::getProviders()[id]; + + // Read one line of each provider + lineInfo[i].bytes.resize(this->m_columnCount); + provider->read(row * this->m_columnCount, lineInfo[i].bytes.data(), lineInfo[i].bytes.size()); + lineInfo[i].validBytes = std::min(this->m_columnCount, provider->getSize() - row * this->m_columnCount); + + // Calculate address width + u8 addressDigits = 0; + for (size_t n = provider->getSize() - 1; n > 0; n >>= 4) + addressDigits++; + + addressDigitCount = std::max(addressDigits, addressDigitCount); + } + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + auto glyphWidth = ImGui::CalcTextSize("0").x + 1; + static auto highlightSize = ImGui::CalcTextSize("00"); + + auto startY = ImGui::GetCursorPosY(); + + ImGui::TextUnformatted(hex::format(this->m_upperCaseHex ? "{:0{}X}:" : "{:0{}x}:", row * this->m_columnCount, addressDigitCount).c_str()); + ImGui::SetCursorPosY(startY); + ImGui::TableNextColumn(); + + const ImColor colorText = ImGui::GetColorU32(ImGuiCol_Text); + const ImColor colorDisabled = this->m_greyedOutZeros ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : static_cast(colorText); + + + for (s8 curr = 0; curr < 2; curr++) { + auto other = !curr; + + std::optional lastHighlightEnd; + + for (s64 col = 0; col < lineInfo[curr].validBytes; col++) { + auto pos = ImGui::GetCursorScreenPos(); + + // Diff bytes + std::optional highlightColor; + switch (diffBytes(col, lineInfo[curr], lineInfo[other])) { + default: + case DiffResult::Same: + /* No highlight */ + break; + case DiffResult::Changed: + highlightColor = getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow)); + break; + case DiffResult::Added: + highlightColor = getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarGreen)); + break; + case DiffResult::Removed: + highlightColor = getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarRed)); + break; + } + + // Draw byte + u8 byte = lineInfo[curr].bytes[col]; + ImGui::TextColored(byte == 0x00 ? colorDisabled : colorText, "%s", hex::format(this->m_upperCaseHex ? "{:02X}" : "{:02x}", byte).c_str()); + ImGui::SetCursorPosY(startY); + + // Draw highlighting + if (highlightColor.has_value()) { + drawList->AddRectFilled(lastHighlightEnd.value_or(pos), pos + highlightSize, highlightColor.value()); + lastHighlightEnd = pos + ImVec2((glyphWidth - 1) * 2, 0); + } else { + lastHighlightEnd.reset(); + } + + ImGui::SameLine(0.0F, col % 8 == 7 ? glyphWidth * 2.5F : glyphWidth * 0.25F); + } + ImGui::TableNextColumn(); + } + + } + + void ViewDiff::drawContent() { + if (ImGui::Begin(View::toWindowName("hex.view.diff.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { + + ImGui::SameLine(); + ImGui::PushID(1); + drawProviderSelector(this->m_providerA); + ImGui::PopID(); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + ImGui::TextUnformatted("<=>"); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + ImGui::PushID(2); + drawProviderSelector(this->m_providerB); + ImGui::PopID(); + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(20, 1)); + if (ImGui::BeginTable("diff", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupScrollFreeze(0, 1); + + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + + // Draw header line + { + auto glyphWidth = ImGui::CalcTextSize("0").x + 1; + for (u8 i = 0; i < 2; i++) { + for (u8 col = 0; col < this->m_columnCount; col++) { + ImGui::TextUnformatted(hex::format(this->m_upperCaseHex ? "{:02X}" : "{:02x}", col).c_str()); + ImGui::SameLine(0.0F, col % 8 == 7 ? glyphWidth * 2.5F : glyphWidth * 0.25F); + } + ImGui::TableNextColumn(); + } + } + + + if (this->m_providerA >= 0 && this->m_providerB >= 0) { + auto &providers = ImHexApi::Provider::getProviders(); + ImGuiListClipper clipper; + clipper.Begin(std::max(providers[this->m_providerA]->getSize() / this->m_columnCount, providers[this->m_providerB]->getSize() / this->m_columnCount)); + + // Draw diff lines + while (clipper.Step()) { + for (u64 row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + drawDiffLine({this->m_providerA, this->m_providerB}, row); + } + } + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } ImGui::End(); } - void ViewTools::drawMenu() { + void ViewDiff::drawMenu() { }