feat: Implement Myers' diffing algorithm (#1508)
This commit is contained in:
parent
9d351317b8
commit
a13b5bf8c0
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
2
lib/external/libwolv
vendored
2
lib/external/libwolv
vendored
@ -1 +1 @@
|
||||
Subproject commit f7c78709e89f2c1c293ec60f897881e7e135580b
|
||||
Subproject commit a58bc52eb69d440e1aa1df035fc405579644531b
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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->() {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
1
lib/third_party/edlib
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 931be2b0909985551eb17d767694a6e64e31ebfa
|
@ -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
|
||||
|
@ -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
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "",
|
||||
|
@ -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": "値をコピー",
|
||||
|
@ -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": "값 복사",
|
||||
|
@ -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": "",
|
||||
|
@ -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": "复制值",
|
||||
|
@ -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": "複製數值",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>();
|
||||
|
@ -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());
|
||||
|
24
plugins/diffing/CMakeLists.txt
Normal file
24
plugins/diffing/CMakeLists.txt
Normal 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
|
||||
)
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
14
plugins/diffing/romfs/lang/de_DE.json
Normal file
14
plugins/diffing/romfs/lang/de_DE.json
Normal 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": ""
|
||||
}
|
||||
}
|
21
plugins/diffing/romfs/lang/en_US.json
Normal file
21
plugins/diffing/romfs/lang/en_US.json
Normal 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"
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/es_ES.json
Normal file
14
plugins/diffing/romfs/lang/es_ES.json
Normal 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"
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/it_IT.json
Normal file
14
plugins/diffing/romfs/lang/it_IT.json
Normal 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": ""
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/ja_JP.json
Normal file
14
plugins/diffing/romfs/lang/ja_JP.json
Normal 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": ""
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/ko_KR.json
Normal file
14
plugins/diffing/romfs/lang/ko_KR.json
Normal 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": "제거됨"
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/pt_BR.json
Normal file
14
plugins/diffing/romfs/lang/pt_BR.json
Normal 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": ""
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/zh_CN.json
Normal file
14
plugins/diffing/romfs/lang/zh_CN.json
Normal 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": "移除"
|
||||
}
|
||||
}
|
14
plugins/diffing/romfs/lang/zh_TW.json
Normal file
14
plugins/diffing/romfs/lang/zh_TW.json
Normal 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": "已移除"
|
||||
}
|
||||
}
|
187
plugins/diffing/source/content/diffing_algorithms.cpp
Normal file
187
plugins/diffing/source/content/diffing_algorithms.cpp
Normal 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>();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
27
plugins/diffing/source/plugin_diffing.cpp
Normal file
27
plugins/diffing/source/plugin_diffing.cpp
Normal 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>();
|
||||
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user