#include "content/views/view_diff.hpp" #include #include #include #include #include namespace hex::plugin::builtin { ViewDiff::ViewDiff() : View("hex.builtin.view.diff.name") { EventManager::subscribe(this, [this] { { auto columnCount = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.bytes_per_row"); if (columnCount.is_number()) this->m_columnCount = static_cast(columnCount); } { auto greyOutZeros = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.grey_zeros"); if (greyOutZeros.is_number()) this->m_greyedOutZeros = static_cast(greyOutZeros); } { auto upperCaseHex = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.uppercase_hex"); if (upperCaseHex.is_number()) this->m_upperCaseHex = static_cast(upperCaseHex); } }); EventManager::subscribe(this, [this](prv::Provider *) { this->m_providerA = -1; this->m_providerB = -1; }); } ViewDiff::~ViewDiff() { EventManager::unsubscribe(this); 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_scaled); if (ImGui::BeginCombo("", preview.c_str())) { for (size_t 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; u64 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 if (row * this->m_columnCount >= provider->getSize()) lineInfo[i].validBytes = 0; else lineInfo[i].validBytes = std::min(this->m_columnCount, provider->getSize() - row * this->m_columnCount); lineInfo[i].bytes.resize(lineInfo[i].validBytes); provider->read(provider->getBaseAddress() + provider->getCurrentPageAddress() + row * this->m_columnCount, lineInfo[i].bytes.data(), lineInfo[i].validBytes); // 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::TableNextColumn(); ImGui::TextFormatted(this->m_upperCaseHex ? "{:0{}X}:" : "{:0{}x}:", row * this->m_columnCount, addressDigitCount); ImGui::SetCursorPosY(startY); const ImColor colorText = ImGui::GetColorU32(ImGuiCol_Text); const ImColor colorDisabled = this->m_greyedOutZeros ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : static_cast(colorText); for (i8 curr = 0; curr < 2; curr++) { ImGui::TableNextColumn(); auto other = !curr; bool hasLastHighlight = false; ImVec2 lastHighlightEnd = { }; for (u64 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::TextFormattedColored(byte == 0x00 ? colorDisabled : colorText, this->m_upperCaseHex ? "{:02X}" : "{:02x}", byte); ImGui::SameLine(0.0F, col % 8 == 7 ? glyphWidth * 1.5F : glyphWidth * 0.75F); ImGui::SetCursorPosY(startY); // Draw highlighting if (highlightColor.has_value()) { if (hasLastHighlight) drawList->AddRectFilled(lastHighlightEnd, pos + highlightSize, highlightColor.value()); else drawList->AddRectFilled(pos, pos + highlightSize, highlightColor.value()); hasLastHighlight = true; lastHighlightEnd = pos + ImVec2((glyphWidth - 1) * 2, 0); } else { hasLastHighlight = false; } } } } void ViewDiff::drawContent() { if (ImGui::Begin(View::toWindowName("hex.builtin.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, 0)); if (ImGui::BeginTable("diff", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableNextRow(); ImGui::TableNextColumn(); // Draw header line { auto glyphWidth = ImGui::CalcTextSize("0").x + 1; for (u8 i = 0; i < 2; i++) { ImGui::TableNextColumn(); for (u32 col = 0; col < this->m_columnCount; col++) { ImGui::TextFormatted(this->m_upperCaseHex ? "{:02X}" : "{:02x}", col); ImGui::SameLine(0.0F, col % 8 == 7 ? glyphWidth * 1.5F : glyphWidth * 0.75F); } } } 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) + 1, ImGui::GetTextLineHeight()); // Draw diff lines while (clipper.Step()) { for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { ImGui::TableNextRow(); drawDiffLine({ this->m_providerA, this->m_providerB }, row); } } } ImGui::EndTable(); } ImGui::PopStyleVar(); } ImGui::End(); } }