1
0
mirror of synced 2024-11-27 17:10:51 +01:00

feat: Implement Myers' diffing algorithm (#1508)

This commit is contained in:
Nik 2024-01-21 18:39:13 +01:00 committed by GitHub
parent 9d351317b8
commit a13b5bf8c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 624 additions and 215 deletions

3
.gitmodules vendored
View File

@ -36,3 +36,6 @@
[submodule "lib/third_party/HashLibPlus"]
path = lib/third_party/HashLibPlus
url = https://github.com/WerWolv/HashLibPlus
[submodule "lib/third_party/edlib"]
path = lib/third_party/edlib
url = https://github.com/Martinsos/edlib

View File

@ -350,6 +350,10 @@ macro(configureCMake)
# display a warning about options being set using set() instead of option().
# Explicitly set the policy to NEW to suppress the warning.
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "Disable deprecated warnings" FORCE)
endmacro()
macro(setDefaultBuiltTypeIfUnset)

@ -1 +1 @@
Subproject commit f7c78709e89f2c1c293ec60f897881e7e135580b
Subproject commit a58bc52eb69d440e1aa1df035fc405579644531b

View File

@ -996,6 +996,56 @@ namespace hex {
}
/* Diffing Registry. Allows adding new diffing algorithms */
namespace Diffing {
enum class DifferenceType : u8 {
Match = 0,
Insertion = 1,
Deletion = 2,
Mismatch = 3
};
using DiffTree = wolv::container::IntervalTree<DifferenceType>;
class Algorithm {
public:
explicit Algorithm(UnlocalizedString unlocalizedName, UnlocalizedString unlocalizedDescription)
: m_unlocalizedName(std::move(unlocalizedName)),
m_unlocalizedDescription(std::move(unlocalizedDescription)) { }
virtual ~Algorithm() = default;
virtual std::vector<DiffTree> analyze(prv::Provider *providerA, prv::Provider *providerB) const = 0;
virtual void drawSettings() { }
const UnlocalizedString& getUnlocalizedName() const { return m_unlocalizedName; }
const UnlocalizedString& getUnlocalizedDescription() const { return m_unlocalizedDescription; }
private:
UnlocalizedString m_unlocalizedName, m_unlocalizedDescription;
};
namespace impl {
std::vector<std::unique_ptr<Algorithm>> &getAlgorithms();
void addAlgorithm(std::unique_ptr<Algorithm> &&hash);
}
/**
* @brief Adds a new hash
* @tparam T The hash type that extends hex::Hash
* @param args The arguments to pass to the constructor of the hash
*/
template<typename T, typename ... Args>
void addAlgorithm(Args && ... args) {
impl::addAlgorithm(std::make_unique<T>(std::forward<Args>(args)...));
}
}
/* Hash Registry. Allows adding new hashes to the Hash view */
namespace Hashes {
@ -1061,6 +1111,7 @@ namespace hex {
std::vector<std::unique_ptr<Hash>> &getHashes();
void add(std::unique_ptr<Hash> &&hash);
}

View File

@ -154,11 +154,25 @@ namespace hex {
*/
static void runWhenTasksFinished(const std::function<void()> &function);
/**
* @brief Sets the name of the current thread
* @param name Name of the thread
*/
static void setCurrentThreadName(const std::string &name);
/**
* @brief Gets the name of the current thread
* @return Name of the thread
*/
static std::string getCurrentThreadName();
/**
* @brief Cleans up finished tasks
*/
static void collectGarbage();
static Task& getCurrentTask();
static size_t getRunningTaskCount();
static size_t getRunningBackgroundTaskCount();

View File

@ -22,8 +22,6 @@ namespace hex {
PerProvider& operator=(const PerProvider&) = delete;
PerProvider& operator=(PerProvider &&) = delete;
PerProvider(T data) : m_data({ { ImHexApi::Provider::get(), std::move(data) } }) { this->onCreate(); }
~PerProvider() { this->onDestroy(); }
T* operator->() {

View File

@ -995,6 +995,23 @@ namespace hex {
return nullptr;
}
}
namespace ContentRegistry::Diffing {
namespace impl {
std::vector<std::unique_ptr<Algorithm>>& getAlgorithms() {
static std::vector<std::unique_ptr<Algorithm>> algorithms;
return algorithms;
}
void addAlgorithm(std::unique_ptr<Algorithm> &&hash) {
getAlgorithms().emplace_back(std::move(hash));
}
}
}

View File

@ -31,6 +31,7 @@ namespace hex {
std::vector<std::jthread> s_workers;
thread_local std::array<char, 256> s_currentThreadName;
thread_local Task* s_currentTask = nullptr;
}
@ -228,6 +229,8 @@ namespace hex {
// Grab the next task from the queue
task = std::move(s_taskQueue.front());
s_taskQueue.pop_front();
s_currentTask = task.get();
}
try {
@ -253,6 +256,7 @@ namespace hex {
task->exception("Unknown Exception");
}
s_currentTask = nullptr;
task->finish();
}
});
@ -326,6 +330,11 @@ namespace hex {
}
Task& TaskManager::getCurrentTask() {
return *s_currentTask;
}
std::list<std::shared_ptr<Task>> &TaskManager::getRunningTasks() {
return s_tasks;
}

1
lib/third_party/edlib vendored Submodule

@ -0,0 +1 @@
Subproject commit 931be2b0909985551eb17d767694a6e64e31ebfa

View File

@ -102,7 +102,6 @@ add_imhex_plugin(
source/content/views/view_data_processor.cpp
source/content/views/view_constants.cpp
source/content/views/view_store.cpp
source/content/views/view_diff.cpp
source/content/views/view_provider_settings.cpp
source/content/views/view_find.cpp
source/content/views/view_theme_manager.cpp

View File

@ -199,7 +199,7 @@ namespace hex::plugin::builtin {
PatternSourceCode m_sourceCode;
PerProvider<std::vector<std::string>> m_console;
PerProvider<bool> m_executionDone = true;
PerProvider<bool> m_executionDone;
std::mutex m_logMutex;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -566,12 +566,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "Auswahl entfernen",
"hex.builtin.view.data_processor.menu.save_node": "",
"hex.builtin.view.data_processor.name": "Datenprozessor",
"hex.builtin.view.diff.name": "Diffing",
"hex.builtin.view.diff.added": "",
"hex.builtin.view.diff.modified": "",
"hex.builtin.view.diff.provider_a": "",
"hex.builtin.view.diff.provider_b": "",
"hex.builtin.view.diff.removed": "",
"hex.builtin.view.find.binary_pattern": "Binärpattern",
"hex.builtin.view.find.binary_pattern.alignment": "",
"hex.builtin.view.find.context.copy": "Wert kopieren",

View File

@ -672,12 +672,7 @@
"hex.builtin.view.data_processor.menu.remove_selection": "Remove Selected",
"hex.builtin.view.data_processor.menu.save_node": "Save Node",
"hex.builtin.view.data_processor.name": "Data Processor",
"hex.builtin.view.diff.name": "Diffing",
"hex.builtin.view.diff.added": "Added",
"hex.builtin.view.diff.modified": "Modified",
"hex.builtin.view.diff.provider_a": "Provider A",
"hex.builtin.view.diff.provider_b": "Provider B",
"hex.builtin.view.diff.removed": "Removed",
"hex.builtin.view.find.binary_pattern": "Binary Pattern",
"hex.builtin.view.find.binary_pattern.alignment": "Alignment",
"hex.builtin.view.find.context.copy": "Copy Value",

View File

@ -565,12 +565,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "Eliminar Seleccionado",
"hex.builtin.view.data_processor.menu.save_node": "Guardar Nodo",
"hex.builtin.view.data_processor.name": "Procesador de Datos",
"hex.builtin.view.diff.name": "Análisis de diferencias",
"hex.builtin.view.diff.added": "Añadidas",
"hex.builtin.view.diff.modified": "Modificadas",
"hex.builtin.view.diff.provider_a": "Proveedor A",
"hex.builtin.view.diff.provider_b": "Proveedor B",
"hex.builtin.view.diff.removed": "Eliminados",
"hex.builtin.view.find.binary_pattern": "Patrón Binario",
"hex.builtin.view.find.binary_pattern.alignment": "Alineado",
"hex.builtin.view.find.context.copy": "Copiar Valor",

View File

@ -565,12 +565,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "Rimuovi i selezionati",
"hex.builtin.view.data_processor.menu.save_node": "",
"hex.builtin.view.data_processor.name": "Processa Dati",
"hex.builtin.view.diff.name": "Diffing",
"hex.builtin.view.diff.added": "",
"hex.builtin.view.diff.modified": "",
"hex.builtin.view.diff.provider_a": "",
"hex.builtin.view.diff.provider_b": "",
"hex.builtin.view.diff.removed": "",
"hex.builtin.view.find.binary_pattern": "",
"hex.builtin.view.find.binary_pattern.alignment": "",
"hex.builtin.view.find.context.copy": "",

View File

@ -565,12 +565,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "選択部分を削除",
"hex.builtin.view.data_processor.menu.save_node": "",
"hex.builtin.view.data_processor.name": "データプロセッサ",
"hex.builtin.view.diff.name": "比較",
"hex.builtin.view.diff.added": "",
"hex.builtin.view.diff.modified": "",
"hex.builtin.view.diff.provider_a": "",
"hex.builtin.view.diff.provider_b": "",
"hex.builtin.view.diff.removed": "",
"hex.builtin.view.find.binary_pattern": "16進数",
"hex.builtin.view.find.binary_pattern.alignment": "",
"hex.builtin.view.find.context.copy": "値をコピー",

View File

@ -586,12 +586,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "선택 영역 제거",
"hex.builtin.view.data_processor.menu.save_node": "노드 저장",
"hex.builtin.view.data_processor.name": "데이터 프로세서",
"hex.builtin.view.diff.name": "파일 비교",
"hex.builtin.view.diff.added": "추가됨",
"hex.builtin.view.diff.modified": "수정됨",
"hex.builtin.view.diff.provider_a": "공급자 A",
"hex.builtin.view.diff.provider_b": "공급자 B",
"hex.builtin.view.diff.removed": "제거됨",
"hex.builtin.view.find.binary_pattern": "바이너리 패턴",
"hex.builtin.view.find.binary_pattern.alignment": "정렬",
"hex.builtin.view.find.context.copy": "값 복사",

View File

@ -565,12 +565,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "Remover Selecionado",
"hex.builtin.view.data_processor.menu.save_node": "",
"hex.builtin.view.data_processor.name": "Data Processor",
"hex.builtin.view.diff.name": "Diferenciando",
"hex.builtin.view.diff.added": "",
"hex.builtin.view.diff.modified": "",
"hex.builtin.view.diff.provider_a": "",
"hex.builtin.view.diff.provider_b": "",
"hex.builtin.view.diff.removed": "",
"hex.builtin.view.find.binary_pattern": "",
"hex.builtin.view.find.binary_pattern.alignment": "",
"hex.builtin.view.find.context.copy": "",

View File

@ -579,12 +579,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "移除已选",
"hex.builtin.view.data_processor.menu.save_node": "保存节点",
"hex.builtin.view.data_processor.name": "数据处理器",
"hex.builtin.view.diff.name": "差异",
"hex.builtin.view.diff.added": "添加",
"hex.builtin.view.diff.modified": "修改",
"hex.builtin.view.diff.provider_a": "提供者A",
"hex.builtin.view.diff.provider_b": "提供者B",
"hex.builtin.view.diff.removed": "移除",
"hex.builtin.view.find.binary_pattern": "二进制模式",
"hex.builtin.view.find.binary_pattern.alignment": "对齐",
"hex.builtin.view.find.context.copy": "复制值",

View File

@ -579,12 +579,6 @@
"hex.builtin.view.data_processor.menu.remove_selection": "移除所選",
"hex.builtin.view.data_processor.menu.save_node": "儲存節點",
"hex.builtin.view.data_processor.name": "資料處理器",
"hex.builtin.view.diff.name": "差異",
"hex.builtin.view.diff.added": "已新增",
"hex.builtin.view.diff.modified": "已修改",
"hex.builtin.view.diff.provider_a": "提供者 A",
"hex.builtin.view.diff.provider_b": "提供者 B",
"hex.builtin.view.diff.removed": "已移除",
"hex.builtin.view.find.binary_pattern": "二進位模式",
"hex.builtin.view.find.binary_pattern.alignment": "",
"hex.builtin.view.find.context.copy": "複製數值",

View File

@ -65,7 +65,7 @@ Pos=562,201
Size=900,700
Collapsed=0
[Window][###hex.builtin.view.diff.name]
[Window][###hex.diffing.view.diff.name]
Pos=1233,38
Size=687,1021
Collapsed=0
@ -149,7 +149,7 @@ hex.builtin.view.command_palette.name=0
hex.builtin.view.constants.name=0
hex.builtin.view.data_inspector.name=1
hex.builtin.view.data_processor.name=0
hex.builtin.view.diff.name=0
hex.diffing.view.diff.name=0
hex.disassembler.view.disassembler.name=0
hex.builtin.view.find.name=0
hex.hashes.view.hashes.name=0

View File

@ -65,7 +65,7 @@ Pos=562,201
Size=900,700
Collapsed=0
[Window][###hex.builtin.view.diff.name]
[Window][###hex.diffing.view.diff.name]
Pos=1233,38
Size=687,1021
Collapsed=0
@ -150,7 +150,7 @@ hex.builtin.view.command_palette.name=0
hex.builtin.view.constants.name=0
hex.builtin.view.data_inspector.name=0
hex.builtin.view.data_processor.name=0
hex.builtin.view.diff.name=0
hex.diffing.view.diff.name=0
hex.disassembler.view.disassembler.name=0
hex.builtin.view.find.name=0
hex.hashes.view.hashes.name=0

View File

@ -12,7 +12,6 @@
#include "content/views/view_data_processor.hpp"
#include "content/views/view_constants.hpp"
#include "content/views/view_store.hpp"
#include "content/views/view_diff.hpp"
#include "content/views/view_provider_settings.hpp"
#include "content/views/view_find.hpp"
#include "content/views/view_theme_manager.hpp"
@ -38,7 +37,6 @@ namespace hex::plugin::builtin {
ContentRegistry::Views::add<ViewDataProcessor>();
ContentRegistry::Views::add<ViewConstants>();
ContentRegistry::Views::add<ViewStore>();
ContentRegistry::Views::add<ViewDiff>();
ContentRegistry::Views::add<ViewProviderSettings>();
ContentRegistry::Views::add<ViewFind>();
ContentRegistry::Views::add<ViewThemeManager>();

View File

@ -71,7 +71,6 @@ IMHEX_PLUGIN_SUBCOMMANDS() {
};
IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
using namespace hex::plugin::builtin;
hex::log::debug("Using romfs: '{}'", romfs::name());

View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.16)
include(ImHexPlugin)
if (USE_SYSTEM_EDLIB)
find_package(edlib REQUIRED)
else()
add_subdirectory(${THIRD_PARTY_LIBS_FOLDER}/edlib ${CMAKE_CURRENT_BINARY_DIR}/edlib EXCLUDE_FROM_ALL)
endif()
add_imhex_plugin(
NAME
diffing
SOURCES
source/plugin_diffing.cpp
source/content/diffing_algorithms.cpp
source/content/views/view_diff.cpp
INCLUDES
include
LIBRARIES
ui
edlib
)

View File

@ -10,7 +10,7 @@
#include "ui/hex_editor.hpp"
namespace hex::plugin::builtin {
namespace hex::plugin::diffing {
class ViewDiff : public View::Window {
public:
@ -23,21 +23,12 @@ namespace hex::plugin::builtin {
public:
struct Column {
ui::HexEditor hexEditor;
ContentRegistry::Diffing::DiffTree diffTree;
int provider = -1;
i32 scrollLock = 0;
};
enum class DifferenceType : u8 {
Added,
Removed,
Modified
};
struct Diff {
Region region;
DifferenceType type;
};
private:
std::function<std::optional<color_t>(u64, const u8*, size_t)> createCompareFunction(size_t otherIndex) const;
void analyze(prv::Provider *providerA, prv::Provider *providerB);
@ -45,9 +36,9 @@ namespace hex::plugin::builtin {
private:
std::array<Column, 2> m_columns;
std::vector<Diff> m_diffs;
TaskHolder m_diffTask;
std::atomic<bool> m_analyzed = false;
ContentRegistry::Diffing::Algorithm *m_algorithm = nullptr;
};
}

View File

@ -0,0 +1,14 @@
{
"code": "de-DE",
"language": "German",
"country": "Germany",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "Diffing",
"hex.diffing.view.diff.added": "",
"hex.diffing.view.diff.modified": "",
"hex.diffing.view.diff.provider_a": "",
"hex.diffing.view.diff.provider_b": "",
"hex.diffing.view.diff.removed": ""
}
}

View File

@ -0,0 +1,21 @@
{
"code": "en-US",
"country": "United States",
"language": "English",
"translations": {
"hex.diffing.algorithm.simple.name": "Simple byte-by-byte algorithm",
"hex.diffing.algorithm.simple.description": "Naïve O(N) byte-by-byte comparison.\nCan only identify byte modifications and insertions / deletions at the end of the data",
"hex.diffing.algorithm.myers.name": "Myers's bit-vector algorithm",
"hex.diffing.algorithm.myers.description": "Smart O(N*M) diffing algorithm. Can identify modifications, insertions and deletions anywhere in the data",
"hex.diffing.algorithm.myers.settings.window_size": "Window size",
"hex.diffing.view.diff.name": "Diffing",
"hex.diffing.view.diff.added": "Added",
"hex.diffing.view.diff.modified": "Modified",
"hex.diffing.view.diff.provider_a": "Provider A",
"hex.diffing.view.diff.provider_b": "Provider B",
"hex.diffing.view.diff.removed": "Removed",
"hex.diffing.view.diff.algorithm": "Diffing Algorithm",
"hex.diffing.view.diff.settings": "No settings available",
"hex.diffing.view.diff.settings.no_settings": "No settings available"
}
}

View File

@ -0,0 +1,14 @@
{
"code": "es_ES",
"language": "Spanish",
"country": "Spain",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "Análisis de diferencias",
"hex.diffing.view.diff.added": "Añadidas",
"hex.diffing.view.diff.modified": "Modificadas",
"hex.diffing.view.diff.provider_a": "Proveedor A",
"hex.diffing.view.diff.provider_b": "Proveedor B",
"hex.diffing.view.diff.removed": "Eliminados"
}
}

View File

@ -0,0 +1,14 @@
{
"code": "it-IT",
"language": "Italian",
"country": "Italy",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "Diffing",
"hex.diffing.view.diff.added": "",
"hex.diffing.view.diff.modified": "",
"hex.diffing.view.diff.provider_a": "",
"hex.diffing.view.diff.provider_b": "",
"hex.diffing.view.diff.removed": ""
}
}

View File

@ -0,0 +1,14 @@
{
"code": "ja-JP",
"language": "Japanese",
"country": "Japan",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "比較",
"hex.diffing.view.diff.added": "",
"hex.diffing.view.diff.modified": "",
"hex.diffing.view.diff.provider_a": "",
"hex.diffing.view.diff.provider_b": "",
"hex.diffing.view.diff.removed": ""
}
}

View File

@ -0,0 +1,14 @@
{
"code": "ko-KR",
"language": "Korean",
"country": "Korea",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "파일 비교",
"hex.diffing.view.diff.added": "추가됨",
"hex.diffing.view.diff.modified": "수정됨",
"hex.diffing.view.diff.provider_a": "공급자 A",
"hex.diffing.view.diff.provider_b": "공급자 B",
"hex.diffing.view.diff.removed": "제거됨"
}
}

View File

@ -0,0 +1,14 @@
{
"code": "pt-BR",
"language": "Portuguese",
"country": "Brazil",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "Diferenciando",
"hex.diffing.view.diff.added": "",
"hex.diffing.view.diff.modified": "",
"hex.diffing.view.diff.provider_a": "",
"hex.diffing.view.diff.provider_b": "",
"hex.diffing.view.diff.removed": ""
}
}

View File

@ -0,0 +1,14 @@
{
"code": "zh-CN",
"language": "Chinese (Simplified)",
"country": "China",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "差异",
"hex.diffing.view.diff.added": "添加",
"hex.diffing.view.diff.modified": "修改",
"hex.diffing.view.diff.provider_a": "提供者A",
"hex.diffing.view.diff.provider_b": "提供者B",
"hex.diffing.view.diff.removed": "移除"
}
}

View File

@ -0,0 +1,14 @@
{
"code": "zh-TW",
"language": "Chinese (Traditional)",
"country": "Taiwan",
"fallback": false,
"translations": {
"hex.diffing.view.diff.name": "差異",
"hex.diffing.view.diff.added": "已新增",
"hex.diffing.view.diff.modified": "已修改",
"hex.diffing.view.diff.provider_a": "提供者 A",
"hex.diffing.view.diff.provider_b": "提供者 B",
"hex.diffing.view.diff.removed": "已移除"
}
}

View File

@ -0,0 +1,187 @@
#include <hex.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/providers/buffered_reader.hpp>
#include <wolv/utils/guards.hpp>
#include <wolv/literals.hpp>
#include <edlib.h>
#include <imgui.h>
#include <hex/api/task_manager.hpp>
namespace hex::plugin::diffing {
using namespace ContentRegistry::Diffing;
using namespace wolv::literals;
class AlgorithmSimple : public Algorithm {
public:
AlgorithmSimple() : Algorithm("hex.diffing.algorithm.simple.name", "hex.diffing.algorithm.simple.description") {}
[[nodiscard]] std::vector<DiffTree> analyze(prv::Provider *providerA, prv::Provider *providerB) const override {
wolv::container::IntervalTree<DifferenceType> differences;
// Set up readers for both providers
auto readerA = prv::ProviderReader(providerA);
auto readerB = prv::ProviderReader(providerB);
auto &task = TaskManager::getCurrentTask();
// Iterate over both providers and compare the bytes
for (auto itA = readerA.begin(), itB = readerB.begin(); itA < readerA.end() && itB < readerB.end(); ++itA, ++itB) {
// Stop comparing if the diff task was canceled
if (task.wasInterrupted())
break;
// If the bytes are different, find the end of the difference
if (*itA != *itB) {
u64 start = itA.getAddress();
size_t size = 0;
while (itA != readerA.end() && itB != readerB.end() && *itA != *itB) {
++itA;
++itB;
++size;
}
// Add the difference to the list
differences.emplace({ start, (start + size) - 1 }, DifferenceType::Mismatch);
}
// Update the progress bar
task.update(itA.getAddress());
}
auto otherDifferences = differences;
// If one provider is larger than the other, add the extra bytes to the list
if (providerA->getActualSize() != providerB->getActualSize()) {
auto endA = providerA->getActualSize() + 1;
auto endB = providerB->getActualSize() + 1;
if (endA > endB) {
differences.emplace({ endB, endA }, DifferenceType::Insertion);
otherDifferences.emplace({ endB, endA }, DifferenceType::Deletion);
}
else {
differences.emplace({ endA, endB }, DifferenceType::Insertion);
otherDifferences.emplace({ endB, endA }, DifferenceType::Insertion);
}
}
return { differences, otherDifferences };
}
};
class AlgorithmMyers : public Algorithm {
public:
AlgorithmMyers() : Algorithm("hex.diffing.algorithm.myers.name", "hex.diffing.algorithm.myers.description") {}
[[nodiscard]] std::vector<DiffTree> analyze(prv::Provider *providerA, prv::Provider *providerB) const override {
DiffTree differencesA, differencesB;
EdlibAlignConfig edlibConfig;
edlibConfig.k = -1;
edlibConfig.additionalEqualities = nullptr;
edlibConfig.additionalEqualitiesLength = 0;
edlibConfig.mode = EdlibAlignMode::EDLIB_MODE_NW;
edlibConfig.task = EdlibAlignTask::EDLIB_TASK_PATH;
const auto windowStart = std::min(providerA->getBaseAddress(), providerB->getBaseAddress());
const auto windowEnd = std::max(providerA->getBaseAddress() + providerA->getActualSize(), providerB->getBaseAddress() + providerB->getActualSize());
auto &task = TaskManager::getCurrentTask();
for (u64 address = windowStart; address < windowEnd; address += m_windowSize) {
if (task.wasInterrupted())
break;
auto currWindowSizeA = std::min<u64>(m_windowSize, providerA->getActualSize() - address);
auto currWindowSizeB = std::min<u64>(m_windowSize, providerB->getActualSize() - address);
std::vector<u8> dataA(currWindowSizeA), dataB(currWindowSizeB);
providerA->read(address, dataA.data(), dataA.size());
providerB->read(address, dataB.data(), dataB.size());
EdlibAlignResult result = edlibAlign(
reinterpret_cast<const char*>(dataA.data()), dataA.size(),
reinterpret_cast<const char*>(dataB.data()), dataB.size(),
edlibConfig
);
auto currentOperation = DifferenceType(0xFF);
Region regionA = {}, regionB = {};
u64 currentAddressA = 0x00, currentAddressB = 0x00;
const auto insertDifference = [&] {
switch (currentOperation) {
using enum DifferenceType;
case Match:
break;
case Mismatch:
differencesA.insert({ regionA.getStartAddress(), regionA.getEndAddress() }, Mismatch);
differencesB.insert({ regionB.getStartAddress(), regionB.getEndAddress() }, Mismatch);
break;
case Insertion:
differencesA.insert({ regionA.getStartAddress(), regionA.getEndAddress() }, Insertion);
differencesB.insert({ regionB.getStartAddress(), regionB.getEndAddress() }, Insertion);
currentAddressB -= regionA.size;
break;
case Deletion:
differencesA.insert({ regionA.getStartAddress(), regionA.getEndAddress() }, Deletion);
differencesB.insert({ regionB.getStartAddress(), regionB.getEndAddress() }, Deletion);
currentAddressA -= regionB.size;
break;
}
};
for (const u8 alignmentType : std::span(result.alignment, result.alignmentLength)) {
ON_SCOPE_EXIT {
currentAddressA++;
currentAddressB++;
};
if (currentOperation == DifferenceType(alignmentType)) {
regionA.size++;
regionB.size++;
continue;
} else if (currentOperation != DifferenceType(0xFF)) {
insertDifference();
currentOperation = DifferenceType(0xFF);
}
currentOperation = DifferenceType(alignmentType);
regionA.address = currentAddressA;
regionB.address = currentAddressB;
regionA.size = 1;
regionB.size = 1;
}
insertDifference();
task.update(address);
}
return { differencesA, differencesB };
}
void drawSettings() override {
static u64 min = 32_kiB, max = 128_kiB;
ImGui::SliderScalar("hex.diffing.algorithm.myers.settings.window_size"_lang, ImGuiDataType_U64, &m_windowSize, &min, &max, "0x%X");
}
private:
u64 m_windowSize = 64_kiB;
};
void registerDiffingAlgorithms() {
ContentRegistry::Diffing::addAlgorithm<AlgorithmSimple>();
ContentRegistry::Diffing::addAlgorithm<AlgorithmMyers>();
}
}

View File

@ -4,27 +4,21 @@
#include <hex/helpers/fmt.hpp>
#include <hex/providers/buffered_reader.hpp>
#include <wolv/utils/guards.hpp>
namespace hex::plugin::builtin {
namespace hex::plugin::diffing {
namespace {
constexpr u32 getDiffColor(u32 color) {
return (color & 0x00FFFFFF) | 0x40000000;
}
}
ViewDiff::ViewDiff() : View::Window("hex.builtin.view.diff.name", ICON_VS_DIFF_SIDEBYSIDE) {
using DifferenceType = ContentRegistry::Diffing::DifferenceType;
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 *) {
for (u8 i = 0; i < 2; i++) {
m_columns[i].provider = -1;
m_columns[i].hexEditor.setSelectionUnchecked(std::nullopt, std::nullopt);
for (auto &column : m_columns) {
column.provider = -1;
column.hexEditor.setSelectionUnchecked(std::nullopt, std::nullopt);
column.diffTree.clear();
}
m_diffs.clear();
});
// Set the background highlight callbacks for the two hex editor columns
@ -39,8 +33,12 @@ namespace hex::plugin::builtin {
namespace {
bool drawDiffColumn(ViewDiff::Column &column, float height) {
if (height < 0)
return false;
bool scrolled = false;
ImGui::PushID(&column);
ON_SCOPE_EXIT { ImGui::PopID(); };
// Draw the hex editor
float prevScroll = column.hexEditor.getScrollPosition();
@ -53,8 +51,6 @@ namespace hex::plugin::builtin {
column.scrollLock = 5;
}
ImGui::PopID();
return scrolled;
}
@ -94,94 +90,44 @@ namespace hex::plugin::builtin {
}
std::function<std::optional<color_t>(u64, const u8*, size_t)> ViewDiff::createCompareFunction(size_t otherIndex) const {
// Create a function that will handle highlighting the differences between the two providers
// This is a stupidly simple diffing implementation. It will highlight bytes that are different in yellow
// and if one provider is larger than the other it will highlight the extra bytes in green or red depending on which provider is larger
// TODO: Use an actual binary diffing algorithm that searches for the longest common subsequences
return [this, otherIndex](u64 address, const u8 *data, size_t) -> std::optional<color_t> {
const auto &providers = ImHexApi::Provider::getProviders();
auto otherId = m_columns[otherIndex].provider;
// Check if the other provider is valid
if (otherId < 0 || size_t(otherId) >= providers.size())
return std::nullopt;
auto &otherProvider = providers[otherId];
// Handle the case where one provider is larger than the other one
if ((address - otherProvider->getBaseAddress()) > otherProvider->getActualSize()) {
if (otherIndex == 1)
return getDiffColor(ImGuiExt::GetCustomColorU32(ImGuiCustomCol_DiffAdded));
else
return getDiffColor(ImGuiExt::GetCustomColorU32(ImGuiCustomCol_DiffRemoved));
}
// Read the current byte from the other provider
u8 otherByte = 0x00;
otherProvider->read(address, &otherByte, 1);
// Compare the two bytes, highlight both in yellow if they are different
if (otherByte != *data)
return getDiffColor(ImGuiExt::GetCustomColorU32(ImGuiCustomCol_DiffChanged));
// No difference
return std::nullopt;
};
}
void ViewDiff::analyze(prv::Provider *providerA, prv::Provider *providerB) {
auto commonSize = std::min(providerA->getActualSize(), providerB->getActualSize());
m_diffTask = TaskManager::createTask("Diffing...", commonSize, [this, providerA, providerB](Task &task) {
std::vector<Diff> differences;
// Set up readers for both providers
auto readerA = prv::ProviderReader(providerA);
auto readerB = prv::ProviderReader(providerB);
// Iterate over both providers and compare the bytes
for (auto itA = readerA.begin(), itB = readerB.begin(); itA < readerA.end() && itB < readerB.end(); ++itA, ++itB) {
// Stop comparing if the diff task was canceled
if (task.wasInterrupted())
break;
// If the bytes are different, find the end of the difference
if (*itA != *itB) {
u64 start = itA.getAddress();
size_t end = 0;
while (itA != readerA.end() && itB != readerB.end() && *itA != *itB) {
++itA;
++itB;
++end;
}
// Add the difference to the list
differences.push_back(Diff { Region{ start, end }, ViewDiff::DifferenceType::Modified });
}
// Update the progress bar
task.update(itA.getAddress());
}
// If one provider is larger than the other, add the extra bytes to the list
if (providerA->getActualSize() != providerB->getActualSize()) {
auto endA = providerA->getActualSize() + 1;
auto endB = providerB->getActualSize() + 1;
if (endA > endB)
differences.push_back(Diff { Region{ endB, endA - endB }, ViewDiff::DifferenceType::Added });
else
differences.push_back(Diff { Region{ endA, endB - endA }, ViewDiff::DifferenceType::Removed });
}
m_diffTask = TaskManager::createTask("Diffing...", commonSize, [this, providerA, providerB](Task &) {
auto differences = m_algorithm->analyze(providerA, providerB);
// Move the calculated differences over so they can be displayed
m_diffs = std::move(differences);
for (size_t i = 0; i < m_columns.size(); i++) {
auto &column = m_columns[i];
column.diffTree = std::move(differences[i]);
}
m_analyzed = true;
});
}
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() {
auto &[a, b] = m_columns;
@ -206,7 +152,7 @@ namespace hex::plugin::builtin {
}
// 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()) {
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];
@ -214,18 +160,29 @@ namespace hex::plugin::builtin {
this->analyze(providerA, providerB);
}
if (auto &algorithms = ContentRegistry::Diffing::impl::getAlgorithms(); m_algorithm == nullptr && !algorithms.empty())
m_algorithm = algorithms.front().get();
const auto height = ImGui::GetContentRegionAvail().y;
// Draw the two hex editor columns side by side
if (ImGui::BeginTable("##binary_diff", 2, ImGuiTableFlags_None, ImVec2(0, height - 250_scaled))) {
ImGui::TableSetupColumn("hex.builtin.view.diff.provider_a"_lang);
ImGui::TableSetupColumn("hex.builtin.view.diff.provider_b"_lang);
ImGui::TableSetupColumn("hex.diffing.view.diff.provider_a"_lang);
ImGui::TableSetupColumn("hex.diffing.view.diff.provider_b"_lang);
ImGui::TableHeadersRow();
ImVec2 buttonPos;
ImGui::BeginDisabled(m_diffTask.isRunning());
{
// Draw first provider selector
// Draw settings button
ImGui::TableNextColumn();
if (ImGuiExt::DimmedIconButton(ICON_VS_SETTINGS_GEAR, ImGui::GetStyleColorVec4(ImGuiCol_Text)))
ImGui::OpenPopup("DiffingAlgorithmSettings");
buttonPos = ImGui::GetCursorScreenPos();
ImGui::SameLine();
// Draw first provider selector
if (drawProviderSelector(a)) m_analyzed = false;
// Draw second provider selector
@ -234,6 +191,41 @@ namespace hex::plugin::builtin {
}
ImGui::EndDisabled();
ImGui::SetNextWindowPos(buttonPos);
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();
}
ImGui::TableNextRow();
// Draw first hex editor column
@ -270,55 +262,70 @@ namespace hex::plugin::builtin {
// Draw the differences if the providers have been analyzed
if (m_analyzed) {
ImGuiListClipper clipper;
clipper.Begin(int(m_diffs.size()));
while (clipper.Step())
auto &diffTreeA = m_columns[0].diffTree;
auto &diffTreeB = m_columns[1].diffTree;
clipper.Begin(int(diffTreeA.size()));
auto diffIterA = diffTreeA.begin();
auto diffIterB = diffTreeB.begin();
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
ImGui::TableNextRow();
// Prevent the list from trying to access non-existing diffs
if (size_t(i) >= m_diffs.size())
if (size_t(i) >= diffTreeA.size())
break;
ImGui::PushID(i);
const auto &diff = m_diffs[i];
const auto &[startA, restA] = *diffIterA;
const auto &[endA, typeA] = restA;
const auto &[startB, restB] = *diffIterB;
const auto &[endB, typeB] = restB;
std::advance(diffIterA, 1);
std::advance(diffIterB, 1);
// Draw a clickable row for each difference that will select the difference in both hex editors
// Draw start address
ImGui::TableNextColumn();
if (ImGui::Selectable(hex::format("0x{:02X}", diff.region.getStartAddress()).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
a.hexEditor.setSelection(diff.region);
if (ImGui::Selectable(hex::format("0x{:02X}", startA).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
a.hexEditor.setSelection({ startA, ((endA - startA) + 1) });
a.hexEditor.jumpToSelection();
b.hexEditor.setSelection(diff.region);
b.hexEditor.setSelection({ startB, ((endB - startB) + 1) });
b.hexEditor.jumpToSelection();
}
// Draw end address
ImGui::TableNextColumn();
ImGui::TextUnformatted(hex::format("0x{:02X}", diff.region.getEndAddress()).c_str());
ImGui::TextUnformatted(hex::format("0x{:02X}", endA).c_str());
// Draw difference type
ImGui::TableNextColumn();
switch (diff.type) {
case DifferenceType::Modified:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffChanged), "hex.builtin.view.diff.modified"_lang);
switch (typeA) {
case DifferenceType::Mismatch:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffChanged), "hex.diffing.view.diff.modified"_lang);
break;
case DifferenceType::Added:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffAdded), "hex.builtin.view.diff.added"_lang);
case DifferenceType::Insertion:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffAdded), "hex.diffing.view.diff.added"_lang);
break;
case DifferenceType::Removed:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffRemoved), "hex.builtin.view.diff.removed"_lang);
case DifferenceType::Deletion:
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_DiffRemoved), "hex.diffing.view.diff.removed"_lang);
break;
default:
break;
}
ImGui::PopID();
}
}
}
ImGui::EndTable();
}
}
}
}

View File

@ -0,0 +1,27 @@
#include <hex/plugin.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/helpers/logger.hpp>
#include <romfs/romfs.hpp>
#include "content/views/view_diff.hpp"
namespace hex::plugin::diffing {
void registerDiffingAlgorithms();
}
using namespace hex;
using namespace hex::plugin::diffing;
IMHEX_PLUGIN_SETUP("Diffing", "WerWolv", "Support for diffing data") {
hex::log::debug("Using romfs: '{}'", romfs::name());
registerDiffingAlgorithms();
ContentRegistry::Views::add<ViewDiff>();
}

View File

@ -48,6 +48,7 @@ namespace hex::ui {
};
ImGui::PushID(reinterpret_cast<void*>(address));
ON_SCOPE_EXIT { ImGui::PopID(); };
char buffer[2] = { std::isprint(data[0]) ? char(data[0]) : '.', 0x00 };
ImGui::InputText("##editing_input", buffer, 2, TextInputFlags | ImGuiInputTextFlags_CallbackEdit, [](ImGuiInputTextCallbackData *data) -> int {
auto &userData = *static_cast<UserData*>(data->UserData);
@ -59,7 +60,6 @@ namespace hex::ui {
return 0;
}, &userData);
ImGui::PopID();
return userData.editingDone || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Escape);
} else {
@ -326,8 +326,8 @@ namespace hex::ui {
ImGui::GetWindowScrollbarID(window, axis),
axis,
&m_scrollPosition.get(),
(std::ceil(innerRect.Max.y - innerRect.Min.y) / CharacterSize.y) - (m_visibleRowCount - 1),
std::nextafterf(numRows, std::numeric_limits<float>::max()),
(std::ceil(innerRect.Max.y - innerRect.Min.y) / CharacterSize.y),
std::nextafterf(numRows + ImGui::GetWindowSize().y / CharacterSize.y, std::numeric_limits<float>::max()),
roundingCorners);
}
@ -608,6 +608,7 @@ namespace hex::ui {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0));
ImGui::PushID(y);
ON_SCOPE_EXIT { ImGui::PopID(); };
if (ImGui::BeginTable("##encoding_cell", encodingData.size(), ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible)) {
ImGui::TableNextRow();
@ -642,7 +643,6 @@ namespace hex::ui {
ImGui::EndTable();
}
ImGui::PopStyleVar();
ImGui::PopID();
}
}
@ -711,19 +711,19 @@ namespace hex::ui {
}
void HexEditor::drawFooter(const ImVec2 &size) {
if (m_provider != nullptr && m_provider->isReadable()) {
const auto pageCount = std::max<u32>(1, m_provider->getPageCount());
constexpr static u32 MinPage = 1;
const auto windowEndPos = ImGui::GetWindowPos() + size - ImGui::GetStyle().WindowPadding;
ImGui::GetWindowDrawList()->AddLine(windowEndPos - ImVec2(0, size.y - 1_scaled), windowEndPos - size + ImVec2(0, 1_scaled), ImGui::GetColorU32(ImGuiCol_Separator), 2.0_scaled);
const auto windowEndPos = ImGui::GetWindowPos() + size - ImGui::GetStyle().WindowPadding;
ImGui::GetWindowDrawList()->AddLine(windowEndPos - ImVec2(0, size.y - 1_scaled), windowEndPos - size + ImVec2(0, 1_scaled), ImGui::GetColorU32(ImGuiCol_Separator), 2.0_scaled);
if (ImGui::BeginChild("##footer", size, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (ImGui::BeginTable("##footer_table", 3, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Left", ImGuiTableColumnFlags_WidthStretch, 0.5f);
ImGui::TableSetupColumn("Center", ImGuiTableColumnFlags_WidthFixed, 20_scaled);
ImGui::TableSetupColumn("Right", ImGuiTableColumnFlags_WidthStretch, 0.5F);
ImGui::TableNextRow();
if (ImGui::BeginChild("##footer", size, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (ImGui::BeginTable("##footer_table", 3, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Left", ImGuiTableColumnFlags_WidthStretch, 0.5f);
ImGui::TableSetupColumn("Center", ImGuiTableColumnFlags_WidthFixed, 20_scaled);
ImGui::TableSetupColumn("Right", ImGuiTableColumnFlags_WidthStretch, 0.5F);
ImGui::TableNextRow();
if (m_provider != nullptr && m_provider->isReadable()) {
const auto pageCount = std::max<u32>(1, m_provider->getPageCount());
constexpr static u32 MinPage = 1;
// Page slider
ImGui::TableNextColumn();
@ -898,12 +898,12 @@ namespace hex::ui {
ImGui::PopItemWidth();
}
}
ImGui::EndTable();
}
ImGui::EndTable();
}
ImGui::EndChild();
}
ImGui::EndChild();
}
void HexEditor::handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered) {