1
0
mirror of synced 2024-12-25 14:04:51 +01:00
ImHex/plugins/diffing/source/content/views/view_diff.cpp

380 lines
15 KiB
C++
Raw Normal View History

2021-12-07 22:47:41 +01:00
#include "content/views/view_diff.hpp"
2021-09-21 02:48:41 +02:00
2021-09-21 19:54:13 +02:00
#include <hex/api/imhex_api.hpp>
2021-09-21 02:48:41 +02:00
2021-09-21 19:54:13 +02:00
#include <hex/helpers/fmt.hpp>
2023-11-10 20:47:08 +01:00
#include <hex/providers/buffered_reader.hpp>
#include <fonts/codicons_font.h>
#include <wolv/utils/guards.hpp>
2021-09-21 19:54:13 +02:00
namespace hex::plugin::diffing {
2021-09-21 02:48:41 +02:00
using DifferenceType = ContentRegistry::Diffing::DifferenceType;
2021-09-21 19:54:13 +02:00
ViewDiff::ViewDiff() : View::Window("hex.diffing.view.diff.name", ICON_VS_DIFF_SIDEBYSIDE) {
// Clear the selected diff providers when a provider is closed
EventProviderClosed::subscribe(this, [this](prv::Provider *) {
2024-02-11 19:29:02 +01:00
this->reset();
});
EventDataChanged::subscribe(this, [this](prv::Provider *) {
m_analyzed = false;
});
2021-09-21 19:54:13 +02:00
// Set the background highlight callbacks for the two hex editor columns
2023-12-19 13:10:25 +01:00
m_columns[0].hexEditor.setBackgroundHighlightCallback(this->createCompareFunction(1));
m_columns[1].hexEditor.setBackgroundHighlightCallback(this->createCompareFunction(0));
2021-09-21 19:54:13 +02:00
}
ViewDiff::~ViewDiff() {
EventProviderClosed::unsubscribe(this);
2024-02-11 19:29:02 +01:00
EventDataChanged::unsubscribe(this);
2021-09-21 19:54:13 +02:00
}
2023-02-16 08:53:05 +01:00
namespace {
2023-02-16 08:53:05 +01:00
bool drawDiffColumn(ViewDiff::Column &column, float height) {
if (height < 0)
return false;
2023-02-16 08:53:05 +01:00
bool scrolled = false;
ImGui::PushID(&column);
ON_SCOPE_EXIT { ImGui::PopID(); };
// Draw the hex editor
2023-02-16 08:53:05 +01:00
float prevScroll = column.hexEditor.getScrollPosition();
column.hexEditor.draw(height);
float currScroll = column.hexEditor.getScrollPosition();
// Check if the user scrolled the hex editor
2023-02-16 08:53:05 +01:00
if (prevScroll != currScroll) {
scrolled = true;
column.scrollLock = 5;
}
2023-02-16 08:53:05 +01:00
return scrolled;
}
bool drawProviderSelector(ViewDiff::Column &column) {
bool shouldReanalyze = false;
2023-02-16 08:53:05 +01:00
ImGui::PushID(&column);
2021-09-21 19:54:13 +02:00
auto providers = ImHexApi::Provider::getProviders();
2023-02-16 08:53:05 +01:00
auto &providerIndex = column.provider;
2021-09-21 19:54:13 +02:00
// Get the name of the currently selected provider
2023-02-16 08:53:05 +01:00
std::string preview;
if (ImHexApi::Provider::isValid() && providerIndex >= 0)
preview = providers[providerIndex]->getName();
2021-09-21 19:54:13 +02:00
// Draw combobox with all available providers
2023-02-16 08:53:05 +01:00
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("", preview.c_str())) {
for (size_t i = 0; i < providers.size(); i++) {
ImGui::PushID(i + 1);
2023-02-16 08:53:05 +01:00
if (ImGui::Selectable(providers[i]->getName().c_str())) {
providerIndex = i;
shouldReanalyze = true;
}
ImGui::PopID();
2021-09-21 19:54:13 +02:00
}
2023-02-16 08:53:05 +01:00
ImGui::EndCombo();
2021-09-21 19:54:13 +02:00
}
2023-02-16 08:53:05 +01:00
ImGui::PopID();
2023-02-16 08:53:05 +01:00
return shouldReanalyze;
}
2021-09-21 19:54:13 +02:00
}
void ViewDiff::analyze(prv::Provider *providerA, prv::Provider *providerB) {
2024-02-11 19:29:02 +01:00
auto commonSize = std::max(providerA->getActualSize(), providerB->getActualSize());
m_diffTask = TaskManager::createTask("hex.diffing.view.diff.task.diffing"_lang, commonSize, [this, providerA, providerB](Task &) {
auto differences = m_algorithm->analyze(providerA, providerB);
2024-02-11 20:44:21 +01:00
auto providers = ImHexApi::Provider::getProviders();
// Move the calculated differences over so they can be displayed
for (size_t i = 0; i < m_columns.size(); i++) {
auto &column = m_columns[i];
2024-02-11 20:44:21 +01:00
auto &provider = providers[column.provider];
2024-02-11 19:29:02 +01:00
column.differences = differences[i].overlapping({ provider->getBaseAddress(), provider->getBaseAddress() + provider->getActualSize() });
std::ranges::sort(
column.differences,
std::less(),
[](const auto &a) { return a.interval; }
);
column.diffTree = std::move(differences[i]);
}
m_analyzed = true;
});
}
2024-02-11 19:29:02 +01:00
void ViewDiff::reset() {
for (auto &column : m_columns) {
column.provider = -1;
column.hexEditor.setSelectionUnchecked(std::nullopt, std::nullopt);
column.diffTree.clear();
}
}
std::function<std::optional<color_t>(u64, const u8*, size_t)> ViewDiff::createCompareFunction(size_t otherIndex) const {
const auto currIndex = otherIndex == 0 ? 1 : 0;
return [=, this](u64 address, const u8 *, size_t size) -> std::optional<color_t> {
if (!m_analyzed)
return std::nullopt;
const auto matches = m_columns[currIndex].diffTree.overlapping({ address, (address + size) - 1 });
if (matches.empty())
return std::nullopt;
const auto type = matches[0].value;
if (type == DifferenceType::Mismatch) {
return ImGuiExt::GetCustomColorU32(ImGuiCustomCol_DiffChanged);
} else if (type == DifferenceType::Insertion && currIndex == 0) {
return ImGuiExt::GetCustomColorU32(ImGuiCustomCol_DiffAdded);
} else if (type == DifferenceType::Deletion && currIndex == 1) {
return ImGuiExt::GetCustomColorU32(ImGuiCustomCol_DiffRemoved);
}
return std::nullopt;
};
}
void ViewDiff::drawContent() {
2023-12-19 13:10:25 +01:00
auto &[a, b] = m_columns;
2021-09-21 19:54:13 +02:00
a.hexEditor.enableSyncScrolling(false);
b.hexEditor.enableSyncScrolling(false);
2021-09-21 19:54:13 +02:00
if (a.scrollLock > 0) a.scrollLock--;
if (b.scrollLock > 0) b.scrollLock--;
2021-09-21 19:54:13 +02:00
// Change the hex editor providers if the user selected a new provider
{
const auto &providers = ImHexApi::Provider::getProviders();
if (a.provider >= 0 && size_t(a.provider) < providers.size())
a.hexEditor.setProvider(providers[a.provider]);
else
a.hexEditor.setProvider(nullptr);
if (b.provider >= 0 && size_t(b.provider) < providers.size())
b.hexEditor.setProvider(providers[b.provider]);
else
b.hexEditor.setProvider(nullptr);
}
// Analyze the providers if they are valid and the user selected a new provider
if (!m_analyzed && a.provider != -1 && b.provider != -1 && !m_diffTask.isRunning() && m_algorithm != nullptr) {
const auto &providers = ImHexApi::Provider::getProviders();
auto providerA = providers[a.provider];
auto providerB = providers[b.provider];
this->analyze(providerA, providerB);
}
if (auto &algorithms = ContentRegistry::Diffing::impl::getAlgorithms(); m_algorithm == nullptr && !algorithms.empty())
m_algorithm = algorithms.front().get();
2024-07-27 16:51:43 +02:00
static float height = 0;
static bool dragging = false;
const auto availableSize = ImGui::GetContentRegionAvail();
auto diffingColumnSize = availableSize;
diffingColumnSize.y *= 3.5 / 5.0;
diffingColumnSize.y -= ImGui::GetTextLineHeightWithSpacing();
diffingColumnSize.y += height;
if (availableSize.y > 1)
diffingColumnSize.y = std::clamp(diffingColumnSize.y, 1.0F, std::max(1.0F, availableSize.y - ImGui::GetTextLineHeightWithSpacing() * 3));
// Draw the two hex editor columns side by side
2024-07-27 16:51:43 +02:00
if (ImGui::BeginTable("##binary_diff", 2, ImGuiTableFlags_None, diffingColumnSize)) {
ImGui::TableSetupColumn("hex.diffing.view.diff.provider_a"_lang);
ImGui::TableSetupColumn("hex.diffing.view.diff.provider_b"_lang);
ImGui::TableHeadersRow();
2021-09-21 19:54:13 +02:00
2023-12-19 13:10:25 +01:00
ImGui::BeginDisabled(m_diffTask.isRunning());
{
// Draw settings button
ImGui::TableNextColumn();
if (ImGuiExt::DimmedIconButton(ICON_VS_SETTINGS_GEAR, ImGui::GetStyleColorVec4(ImGuiCol_Text)))
RequestOpenPopup::post("##DiffingAlgorithmSettings");
ImGui::SameLine();
// Draw first provider selector
2023-12-19 13:10:25 +01:00
if (drawProviderSelector(a)) m_analyzed = false;
// Draw second provider selector
ImGui::TableNextColumn();
2023-12-19 13:10:25 +01:00
if (drawProviderSelector(b)) m_analyzed = false;
}
ImGui::EndDisabled();
2021-09-21 02:48:41 +02:00
ImGui::TableNextRow();
2021-09-21 02:48:41 +02:00
// Draw first hex editor column
ImGui::TableNextColumn();
2024-07-27 16:51:43 +02:00
bool scrollB = drawDiffColumn(a, diffingColumnSize.y);
// Draw second hex editor column
ImGui::TableNextColumn();
2024-07-27 16:51:43 +02:00
bool scrollA = drawDiffColumn(b, diffingColumnSize.y);
// Sync the scroll positions of the hex editors
{
if (scrollA && a.scrollLock == 0) {
a.hexEditor.setScrollPosition(b.hexEditor.getScrollPosition());
a.hexEditor.forceUpdateScrollPosition();
}
if (scrollB && b.scrollLock == 0) {
b.hexEditor.setScrollPosition(a.hexEditor.getScrollPosition());
b.hexEditor.forceUpdateScrollPosition();
}
}
2021-09-21 19:54:13 +02:00
ImGui::EndTable();
}
2021-09-21 19:54:13 +02:00
2024-07-27 16:51:43 +02:00
ImGui::Button("##table_drag_bar", ImVec2(ImGui::GetContentRegionAvail().x, 2_scaled));
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
if (ImGui::IsItemHovered())
dragging = true;
} else {
dragging = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
}
if (dragging) {
height += ImGui::GetMouseDragDelta(ImGuiMouseButton_Left, 0).y;
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
}
// Draw the differences table
2024-07-27 16:51:43 +02:00
if (ImGui::BeginTable("##differences", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("hex.ui.common.begin"_lang);
ImGui::TableSetupColumn("hex.ui.common.end"_lang);
ImGui::TableSetupColumn("hex.ui.common.type"_lang);
ImGui::TableHeadersRow();
2021-09-21 19:54:13 +02:00
// Draw the differences if the providers have been analyzed
2023-12-19 13:10:25 +01:00
if (m_analyzed) {
ImGuiListClipper clipper;
2024-02-11 19:29:02 +01:00
auto &differencesA = m_columns[0].differences;
auto &differencesB = m_columns[1].differences;
clipper.Begin(int(std::min(differencesA.size(), differencesB.size())));
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
ImGui::TableNextRow();
2021-09-21 19:54:13 +02:00
ImGui::PushID(i);
2021-09-21 19:54:13 +02:00
2024-02-11 19:29:02 +01:00
const auto &[regionA, typeA] = differencesA[i];
const auto &[regionB, typeB] = differencesB[i];
// Draw a clickable row for each difference that will select the difference in both hex editors
// Draw start address
ImGui::TableNextColumn();
2024-02-11 19:29:02 +01:00
if (ImGui::Selectable(hex::format("0x{:02X}", regionA.start).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
const Region selectionA = { regionA.start, ((regionA.end - regionA.start) + 1) };
const Region selectionB = { regionB.start, ((regionB.end - regionB.start) + 1) };
a.hexEditor.setSelection(selectionA);
a.hexEditor.jumpToSelection();
b.hexEditor.setSelection(selectionB);
b.hexEditor.jumpToSelection();
const auto &providers = ImHexApi::Provider::getProviders();
auto openProvider = ImHexApi::Provider::get();
if (providers[a.provider] == openProvider)
ImHexApi::HexEditor::setSelection(selectionA);
else if (providers[b.provider] == openProvider)
ImHexApi::HexEditor::setSelection(selectionB);
}
2021-09-21 19:54:13 +02:00
// Draw end address
ImGui::TableNextColumn();
ImGui::TextUnformatted(hex::format("0x{:02X}", regionA.end).c_str());
// Draw difference type
ImGui::TableNextColumn();
switch (typeA) {
case DifferenceType::Mismatch:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffChanged), "hex.diffing.view.diff.modified"_lang);
break;
case DifferenceType::Insertion:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffAdded), "hex.diffing.view.diff.added"_lang);
break;
case DifferenceType::Deletion:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffRemoved), "hex.diffing.view.diff.removed"_lang);
break;
default:
break;
2021-09-21 19:54:13 +02:00
}
ImGui::PopID();
}
}
2021-09-21 19:54:13 +02:00
}
ImGui::EndTable();
2021-09-21 02:48:41 +02:00
}
}
void ViewDiff::drawAlwaysVisibleContent() {
ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(400_scaled, 600_scaled));
if (ImGui::BeginPopup("##DiffingAlgorithmSettings")) {
ImGuiExt::Header("hex.diffing.view.diff.algorithm"_lang, true);
ImGui::PushItemWidth(300_scaled);
if (ImGui::BeginCombo("##Algorithm", m_algorithm == nullptr ? "" : Lang(m_algorithm->getUnlocalizedName()))) {
for (const auto &algorithm : ContentRegistry::Diffing::impl::getAlgorithms()) {
ImGui::PushID(algorithm.get());
if (ImGui::Selectable(Lang(algorithm->getUnlocalizedName()))) {
m_algorithm = algorithm.get();
m_analyzed = false;
}
ImGui::PopID();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
if (m_algorithm != nullptr) {
ImGuiExt::TextFormattedWrapped("{}", Lang(m_algorithm->getUnlocalizedDescription()));
}
ImGuiExt::Header("hex.diffing.view.diff.settings"_lang);
if (m_algorithm != nullptr) {
auto drawList = ImGui::GetWindowDrawList();
auto prevIdx = drawList->_VtxCurrentIdx;
m_algorithm->drawSettings();
auto currIdx = drawList->_VtxCurrentIdx;
if (prevIdx == currIdx)
ImGuiExt::TextFormatted("hex.diffing.view.diff.settings.no_settings"_lang);
}
ImGui::EndPopup();
}
}
}