1
0
mirror of synced 2024-11-28 09:30:51 +01:00

feat: Added highlighting rules

This commit is contained in:
WerWolv 2023-12-02 11:09:32 +01:00
parent 2cf642a2a4
commit 866cb5706d
9 changed files with 418 additions and 4 deletions

@ -1 +1 @@
Subproject commit 71cddb8d45763168b701dfcd671e9f47f27ac897 Subproject commit aaf491cbc71bb9b610671b82a8c8262af5061212

View File

@ -154,7 +154,7 @@ namespace hex {
public: public:
explicit Floating(std::string unlocalizedName) : Window(std::move(unlocalizedName)) {} explicit Floating(std::string unlocalizedName) : Window(std::move(unlocalizedName)) {}
[[nodiscard]] ImGuiWindowFlags getWindowFlags() const final { return ImGuiWindowFlags_NoDocking; } [[nodiscard]] ImGuiWindowFlags getWindowFlags() const { return ImGuiWindowFlags_NoDocking; }
}; };
/** /**

View File

@ -142,6 +142,10 @@ namespace hex::init {
fs::setFileBrowserErrorCallback(nullptr); fs::setFileBrowserErrorCallback(nullptr);
// Unlock font atlas so it can be deleted in case of a crash
if (ImGui::GetCurrentContext() != nullptr)
ImGui::GetIO().Fonts->Locked = false;
return true; return true;
} }

View File

@ -102,6 +102,7 @@ add_imhex_plugin(
source/content/views/view_theme_manager.cpp source/content/views/view_theme_manager.cpp
source/content/views/view_logs.cpp source/content/views/view_logs.cpp
source/content/views/view_achievements.cpp source/content/views/view_achievements.cpp
source/content/views/view_highlight_rules.cpp
source/content/views/view_yara.cpp source/content/views/view_yara.cpp
source/content/helpers/notification.cpp source/content/helpers/notification.cpp

View File

@ -0,0 +1,79 @@
#pragma once
#include <hex/api/content_registry.hpp>
#include <hex/ui/view.hpp>
#include <list>
#include <wolv/math_eval/math_evaluator.hpp>
namespace hex::plugin::builtin {
class ViewHighlightRules : public View::Floating {
public:
ViewHighlightRules();
~ViewHighlightRules() override = default;
void drawContent() override;
[[nodiscard]] bool hasViewMenuItemEntry() const override { return false; }
ImVec2 getMinSize() const override {
return scaled({700, 400});
}
ImVec2 getMaxSize() const override {
return scaled({700, 400});
}
ImGuiWindowFlags getWindowFlags() const override {
return View::Floating::getWindowFlags() | ImGuiWindowFlags_NoResize;
}
private:
struct Rule {
struct Expression {
Expression(std::string mathExpression, std::array<float, 3> color);
~Expression();
Expression(const Expression&) = delete;
Expression(Expression&&) noexcept;
Expression& operator=(const Expression&) = delete;
Expression& operator=(Expression&&) noexcept;
std::string mathExpression;
std::array<float, 3> color;
u32 highlightId = 0;
Rule *parentRule = nullptr;
static wolv::math_eval::MathEvaluator<i128> s_evaluator;
private:
void addHighlight();
void removeHighlight();
};
explicit Rule(std::string name);
Rule(const Rule &) = delete;
Rule(Rule &&) noexcept;
Rule& operator=(const Rule &) = delete;
Rule& operator=(Rule &&) noexcept;
std::string name;
std::list<Expression> expressions;
bool enabled = true;
void addExpression(Expression &&expression);
};
private:
void drawRulesList();
void drawRulesConfig();
private:
PerProvider<std::list<Rule>> m_rules;
PerProvider<std::list<Rule>::iterator> m_selectedRule;
};
}

View File

@ -940,6 +940,13 @@
"hex.builtin.view.hex_editor.shortcut.cursor_page_up": "Move cursor up one page", "hex.builtin.view.hex_editor.shortcut.cursor_page_up": "Move cursor up one page",
"hex.builtin.view.hex_editor.shortcut.selection_page_down": "Move selection down one page", "hex.builtin.view.hex_editor.shortcut.selection_page_down": "Move selection down one page",
"hex.builtin.view.hex_editor.shortcut.cursor_page_down": "Move cursor down one page", "hex.builtin.view.hex_editor.shortcut.cursor_page_down": "Move cursor down one page",
"hex.builtin.view.highlight_rules.name": "Highlight Rules",
"hex.builtin.view.highlight_rules.new_rule": "New Rule",
"hex.builtin.view.highlight_rules.config": "Config",
"hex.builtin.view.highlight_rules.expression": "Expression",
"hex.builtin.view.highlight_rules.help_text": "Enter a mathematical expression that will be evaluated for each byte in the file.\\n\\nThe expression can use the variables 'value' and 'offset'.\\nIf the expression evaluates to true (result is greater than 0), the byte will be highlighted with the specified color.",
"hex.builtin.view.highlight_rules.no_rule": "Create a rule to edit it",
"hex.builtin.view.highlight_rules.menu.edit.rules": "Modify highlight rules...",
"hex.builtin.view.information.analyze": "Analyze page", "hex.builtin.view.information.analyze": "Analyze page",
"hex.builtin.view.information.analyzing": "Analyzing...", "hex.builtin.view.information.analyzing": "Analyzing...",
"hex.builtin.view.information.block_size": "Block size", "hex.builtin.view.information.block_size": "Block size",

View File

@ -21,6 +21,7 @@
#include "content/views/view_theme_manager.hpp" #include "content/views/view_theme_manager.hpp"
#include "content/views/view_logs.hpp" #include "content/views/view_logs.hpp"
#include "content/views/view_achievements.hpp" #include "content/views/view_achievements.hpp"
#include "content/views/view_highlight_rules.hpp"
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -48,6 +49,7 @@ namespace hex::plugin::builtin {
ContentRegistry::Views::add<ViewThemeManager>(); ContentRegistry::Views::add<ViewThemeManager>();
ContentRegistry::Views::add<ViewLogs>(); ContentRegistry::Views::add<ViewLogs>();
ContentRegistry::Views::add<ViewAchievements>(); ContentRegistry::Views::add<ViewAchievements>();
ContentRegistry::Views::add<ViewHighlightRules>();
} }
} }

View File

@ -508,8 +508,8 @@ namespace hex::plugin::builtin {
if (!commits.empty()) { if (!commits.empty()) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
ImGuiExt::BeginSubWindow("Commits", ImGui::GetContentRegionAvail()); ImGuiExt::BeginSubWindow("Commits", ImGui::GetContentRegionAvail());
ImGui::PopStyleVar();
{ {
if (ImGui::BeginTable("##commits", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY)) { if (ImGui::BeginTable("##commits", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY)) {
// Draw commits // Draw commits
for (const auto &commit : commits) { for (const auto &commit : commits) {
@ -562,7 +562,6 @@ namespace hex::plugin::builtin {
} }
} }
ImGuiExt::EndSubWindow(); ImGuiExt::EndSubWindow();
ImGui::PopStyleVar();
} }
} }

View File

@ -0,0 +1,322 @@
#include "content/views/view_highlight_rules.hpp"
#include <hex/api/content_registry.hpp>
#include <hex/api/project_file_manager.hpp>
#include <wolv/utils/guards.hpp>
namespace hex::plugin::builtin {
wolv::math_eval::MathEvaluator<i128> ViewHighlightRules::Rule::Expression::s_evaluator;
ViewHighlightRules::Rule::Rule(std::string name) : name(std::move(name)) { }
ViewHighlightRules::Rule::Rule(Rule &&other) noexcept {
this->name = std::move(other.name);
this->expressions = std::move(other.expressions);
this->enabled = other.enabled;
// Set the parent rule to the new rule for all expressions
for (auto &expression : this->expressions)
expression.parentRule = this;
}
ViewHighlightRules::Rule& ViewHighlightRules::Rule::operator=(Rule &&other) noexcept {
this->name = std::move(other.name);
this->expressions = std::move(other.expressions);
this->enabled = other.enabled;
// Set the parent rule to the new rule for all expressions
for (auto &expression : this->expressions)
expression.parentRule = this;
return *this;
}
void ViewHighlightRules::Rule::addExpression(Expression &&expression) {
// Add the expression to the list and set the parent rule
expression.parentRule = this;
this->expressions.emplace_back(std::move(expression));
}
ViewHighlightRules::Rule::Expression::Expression(std::string mathExpression, std::array<float, 3> color) : mathExpression(std::move(mathExpression)), color(color) {
// Create a new highlight provider function for this expression
this->addHighlight();
}
ViewHighlightRules::Rule::Expression::~Expression() {
// If there was a highlight, remove it
if (this->highlightId > 0)
ImHexApi::HexEditor::removeForegroundHighlightingProvider(this->highlightId);
}
ViewHighlightRules::Rule::Expression::Expression(Expression &&other) noexcept : mathExpression(std::move(other.mathExpression)), color(other.color), parentRule(other.parentRule) {
// Remove the highlight from the other expression and add a new one for this one
// This is necessary as the highlight provider function holds a reference to the expression
// so to avoid dangling references, we need to destroy the old one before the expression itself
// is deconstructed
other.removeHighlight();
this->addHighlight();
}
ViewHighlightRules::Rule::Expression& ViewHighlightRules::Rule::Expression::operator=(Expression &&other) noexcept {
this->mathExpression = std::move(other.mathExpression);
this->color = other.color;
this->parentRule = other.parentRule;
// Remove the highlight from the other expression and add a new one for this one
other.removeHighlight();
this->addHighlight();
return *this;
}
void ViewHighlightRules::Rule::Expression::addHighlight() {
this->highlightId = ImHexApi::HexEditor::addForegroundHighlightingProvider([this](u64 offset, const u8 *buffer, size_t size, bool) -> std::optional<color_t>{
// If the rule containing this expression is disabled, don't highlight anything
if (!this->parentRule->enabled)
return std::nullopt;
// If the expression is empty, don't highlight anything
if (this->mathExpression.empty())
return std::nullopt;
// Load the bytes that are being highlighted into a variable
u64 value = 0;
std::memcpy(&value, buffer, std::min(sizeof(value), size));
// Add the value and offset variables to the evaluator
s_evaluator.setVariable("value", value);
s_evaluator.setVariable("offset", offset);
// Evaluate the expression
auto result = s_evaluator.evaluate(this->mathExpression);
// If the evaluator has returned a value and it's not 0, return the selected color
if (result.has_value() && result.value() != 0)
return ImGui::ColorConvertFloat4ToU32(ImVec4(this->color[0], this->color[1], this->color[2], 1.0F));
else
return std::nullopt;
});
ImHexApi::Provider::markDirty();
}
void ViewHighlightRules::Rule::Expression::removeHighlight() {
ImHexApi::HexEditor::removeForegroundHighlightingProvider(this->highlightId);
this->highlightId = 0;
ImHexApi::Provider::markDirty();
}
ViewHighlightRules::ViewHighlightRules() : View::Floating("hex.builtin.view.highlight_rules.name") {
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.highlight_rules.menu.edit.rules" }, 1870, Shortcut::None, [&, this] {
this->getWindowOpenState() = true;
}, ImHexApi::Provider::isValid);
ProjectFile::registerPerProviderHandler({
.basePath = "highlight_rules.json",
.required = false,
.load = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) -> bool {
const auto json = nlohmann::json::parse(tar.readString(basePath));
auto &rules = this->m_rules.get(provider);
rules.clear();
for (const auto &entry : json) {
Rule rule(entry["name"].get<std::string>());
rule.enabled = entry["enabled"].get<bool>();
for (const auto &expression : entry["expressions"]) {
rule.addExpression(Rule::Expression(
expression["mathExpression"].get<std::string>(),
expression["color"].get<std::array<float, 3>>()
));
}
rules.emplace_back(std::move(rule));
}
return true;
},
.store = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) -> bool {
nlohmann::json result = nlohmann::json::array();
for (const auto &rule : this->m_rules.get(provider)) {
nlohmann::json content;
content["name"] = rule.name;
content["enabled"] = rule.enabled;
for (const auto &expression : rule.expressions) {
content["expressions"].push_back({
{ "mathExpression", expression.mathExpression },
{ "color", expression.color }
});
}
result.push_back(content);
}
tar.writeString(basePath, result.dump(4));
return true;
}
});
// Initialize the selected rule iterators to point to the end of the rules lists
this->m_selectedRule = this->m_rules->end();
EventManager::subscribe<EventProviderCreated>([this](prv::Provider *provider) {
this->m_selectedRule.get(provider) = this->m_rules.get(provider).end();
});
}
void ViewHighlightRules::drawRulesList() {
// Draw a table containing all the existing highlighting rules
if (ImGui::BeginTable("RulesList", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY, ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y))) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 1);
ImGui::TableSetupColumn("Enabled", ImGuiTableColumnFlags_WidthFixed, 10_scaled);
for (auto it = this->m_rules->begin(); it != this->m_rules->end(); ++it) {
auto &rule = *it;
ImGui::TableNextRow();
ImGui::TableNextColumn();
// Add a selectable for each rule to be able to switch between them
ImGui::PushID(&rule);
ImGui::BeginDisabled(!rule.enabled);
if (ImGui::Selectable(rule.name.c_str(), this->m_selectedRule == it, ImGuiSelectableFlags_SpanAvailWidth)) {
this->m_selectedRule = it;
}
ImGui::EndDisabled();
// Draw enabled checkbox
ImGui::TableNextColumn();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
if (ImGui::Checkbox("##enabled", &rule.enabled)) {
EventManager::post<EventHighlightingChanged>();
}
ImGui::PopStyleVar();
ImGui::PopID();
}
ImGui::EndTable();
}
// Draw button to add a new rule
if (ImGuiExt::DimmedIconButton(ICON_VS_ADD, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
this->m_rules->emplace_back("hex.builtin.view.highlight_rules.new_rule"_lang);
if (this->m_selectedRule == this->m_rules->end())
this->m_selectedRule = this->m_rules->begin();
}
ImGui::SameLine();
// Draw button to remove the selected rule
ImGui::BeginDisabled(this->m_selectedRule == this->m_rules->end());
if (ImGuiExt::DimmedIconButton(ICON_VS_REMOVE, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
auto next = std::next(*this->m_selectedRule);
this->m_rules->erase(*this->m_selectedRule);
this->m_selectedRule = next;
}
ImGui::EndDisabled();
}
void ViewHighlightRules::drawRulesConfig() {
ImGuiExt::BeginSubWindow("hex.builtin.view.highlight_rules.config"_lang, ImGui::GetContentRegionAvail());
{
if (this->m_selectedRule != this->m_rules->end()) {
// Draw text input field for the rule name
ImGui::PushItemWidth(-1);
ImGui::InputTextWithHint("##name", "Name", this->m_selectedRule.get()->name);
ImGui::PopItemWidth();
auto &rule = *this->m_selectedRule;
// Draw a table containing all the expressions for the selected rule
ImGui::PushID(&rule);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2());
if (ImGui::BeginTable("Expressions", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y))) {
ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 19_scaled);
ImGui::TableSetupColumn("Expression", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##Remove", ImGuiTableColumnFlags_WidthFixed, 19_scaled);
for (auto it = rule->expressions.begin(); it != rule->expressions.end(); ++it) {
auto &expression = *it;
bool updateHighlight = false;
ImGui::PushID(&expression);
ON_SCOPE_EXIT { ImGui::PopID(); };
ImGui::TableNextRow();
// Draw color picker
ImGui::TableNextColumn();
updateHighlight = ImGui::ColorEdit3("##color", expression.color.data(), ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoBorder) || updateHighlight;
// Draw math expression input field
ImGui::TableNextColumn();
ImGui::PushItemWidth(-1);
updateHighlight = ImGui::InputTextWithHint("##expression", "hex.builtin.view.highlight_rules.expression"_lang, expression.mathExpression) || updateHighlight;
ImGui::PopItemWidth();
// Draw a button to remove the expression
ImGui::TableNextColumn();
if (ImGuiExt::DimmedIconButton(ICON_VS_REMOVE, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
rule->expressions.erase(it);
break;
}
// If any of the inputs have changed, update the highlight
if (updateHighlight)
EventManager::post<EventHighlightingChanged>();
}
ImGui::EndTable();
}
ImGui::PopStyleVar();
// Draw button to add a new expression
if (ImGuiExt::DimmedIconButton(ICON_VS_ADD, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
this->m_selectedRule.get()->addExpression(Rule::Expression("", {}));
ImHexApi::Provider::markDirty();
}
ImGui::SameLine();
// Draw help info for the expressions
ImGuiExt::HelpHover("hex.builtin.view.highlight_rules.help_text"_lang);
ImGui::PopID();
} else {
ImGuiExt::TextFormattedCentered("hex.builtin.view.highlight_rules.no_rule"_lang);
}
}
ImGuiExt::EndSubWindow();
}
void ViewHighlightRules::drawContent() {
if (ImGui::BeginTable("Layout", 2)) {
ImGui::TableSetupColumn("##left", ImGuiTableColumnFlags_WidthStretch, 0.33F);
ImGui::TableSetupColumn("##right", ImGuiTableColumnFlags_WidthStretch, 0.66F);
ImGui::TableNextRow();
// Draw rules list
ImGui::TableNextColumn();
this->drawRulesList();
// Draw rules config
ImGui::TableNextColumn();
this->drawRulesConfig();
ImGui::EndTable();
}
}
}