nodes: Added Digram visualizer node
This commit is contained in:
parent
f9668f4ba6
commit
ca57f91bfa
@ -173,7 +173,7 @@ namespace hex::init {
|
||||
|
||||
// On Macs with a retina display (basically all modern ones we care about), the OS reports twice
|
||||
// the actual monitor scale for some obscure reason. Get rid of this here so ImHex doesn't look
|
||||
// extremely huge with native scaling on MacOS.
|
||||
// extremely huge with native scaling on macOS.
|
||||
#if defined(OS_MACOS)
|
||||
meanScale /= 2;
|
||||
#endif
|
||||
|
@ -35,6 +35,8 @@ namespace hex::plugin::builtin {
|
||||
|
||||
std::optional<dp::Node::NodeError> m_currNodeError;
|
||||
|
||||
bool m_continuousEvaluation = false;
|
||||
|
||||
void eraseLink(u32 id);
|
||||
void eraseNodes(const std::vector<int> &ids);
|
||||
void processNodes();
|
||||
|
@ -1,18 +1,20 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/data_processor/node.hpp>
|
||||
|
||||
#include <hex/api/localization.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/api/localization.hpp>
|
||||
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/providers/provider.hpp>
|
||||
|
||||
#include <cctype>
|
||||
#include <random>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <hex/ui/imgui_data_visualizers.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
@ -751,6 +753,89 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
};
|
||||
|
||||
class NodeVisualizerDigram : public dp::Node {
|
||||
public:
|
||||
NodeVisualizerDigram() : Node("hex.builtin.nodes.visualizer.digram.header", { dp::Attribute(dp::Attribute::IOType::In, dp::Attribute::Type::Buffer, "hex.builtin.nodes.visualizer.digram.input") }) { }
|
||||
|
||||
void drawNode() override {
|
||||
const auto viewSize = scaled({ 200, 200 });
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImU32(ImColor(0, 0, 0)));
|
||||
if (ImGui::BeginChild("##visualizer", viewSize, true)) {
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
float xStep = (viewSize.x * 0.95F) / 0xFF;
|
||||
float yStep = (viewSize.y * 0.95F) / 0xFF;
|
||||
|
||||
for (size_t i = 0; i < ((this->m_buffer.size() == 0) ? 0 : this->m_buffer.size() - 1); i++) {
|
||||
const auto &[x, y] = std::pair { this->m_buffer[i] * xStep, this->m_buffer[i + 1] * yStep };
|
||||
|
||||
auto color = ImLerp(ImColor(0xFF, 0x6D, 0x01).Value, ImColor(0x01, 0x93, 0xFF).Value, float(i) / this->m_buffer.size());
|
||||
color.w = this->m_opacityBuffer[i];
|
||||
|
||||
auto pos = ImGui::GetWindowPos() + ImVec2(viewSize.x * 0.025F, viewSize.y * 0.025F) + ImVec2(x, y);
|
||||
drawList->AddRectFilled(pos, pos + ImVec2(xStep, yStep), ImColor(color));
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void process() override {
|
||||
constexpr static auto SampleSize = 0x9000;
|
||||
const static size_t SequenceCount = std::ceil(std::sqrt(SampleSize));
|
||||
|
||||
this->m_buffer.clear();
|
||||
|
||||
auto buffer = this->getBufferOnInput(0);
|
||||
if (buffer.size() < SampleSize)
|
||||
this->m_buffer = buffer;
|
||||
else {
|
||||
std::random_device randomDevice;
|
||||
std::mt19937_64 random(randomDevice());
|
||||
|
||||
std::map<u64, std::vector<u8>> orderedData;
|
||||
for (u32 i = 0; i < SequenceCount; i++) {
|
||||
ssize_t offset = random() % buffer.size();
|
||||
|
||||
std::vector<u8> sequence;
|
||||
sequence.reserve(SampleSize);
|
||||
std::copy(buffer.begin() + offset, buffer.begin() + offset + std::min<size_t>(SequenceCount, buffer.size() - offset), std::back_inserter(sequence));
|
||||
|
||||
orderedData.insert({ offset, sequence });
|
||||
}
|
||||
|
||||
this->m_buffer.reserve(SampleSize);
|
||||
|
||||
u64 lastEnd = 0x00;
|
||||
for (const auto &[offset, sequence] : orderedData) {
|
||||
if (offset < lastEnd)
|
||||
this->m_buffer.resize(this->m_buffer.size() - (lastEnd - offset));
|
||||
|
||||
std::copy(sequence.begin(), sequence.end(), std::back_inserter(this->m_buffer));
|
||||
lastEnd = offset + sequence.size();
|
||||
}
|
||||
}
|
||||
|
||||
this->m_opacityBuffer.resize(this->m_buffer.size());
|
||||
|
||||
std::map<u64, size_t> heatMap;
|
||||
for (size_t i = 0; i < (this->m_buffer.empty() ? 0 : this->m_buffer.size() - 1); i++) {
|
||||
auto count = ++heatMap[this->m_buffer[i] << 8 | heatMap[i + 1]];
|
||||
|
||||
this->m_highestCount = std::max(this->m_highestCount, count);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (this->m_buffer.empty() ? 0 : this->m_buffer.size() - 1); i++) {
|
||||
this->m_opacityBuffer[i] = std::min(0.2F + (float(heatMap[this->m_buffer[i] << 8 | this->m_buffer[i + 1]]) / float(this->m_highestCount / 1000)), 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> m_buffer;
|
||||
std::vector<float> m_opacityBuffer;
|
||||
size_t m_highestCount = 0;
|
||||
};
|
||||
|
||||
void registerDataProcessorNodes() {
|
||||
ContentRegistry::DataProcessorNode::add<NodeInteger>("hex.builtin.nodes.constants", "hex.builtin.nodes.constants.int");
|
||||
ContentRegistry::DataProcessorNode::add<NodeFloat>("hex.builtin.nodes.constants", "hex.builtin.nodes.constants.float");
|
||||
@ -797,6 +882,8 @@ namespace hex::plugin::builtin {
|
||||
ContentRegistry::DataProcessorNode::add<NodeDecodingHex>("hex.builtin.nodes.decoding", "hex.builtin.nodes.decoding.hex");
|
||||
|
||||
ContentRegistry::DataProcessorNode::add<NodeCryptoAESDecrypt>("hex.builtin.nodes.crypto", "hex.builtin.nodes.crypto.aes");
|
||||
|
||||
ContentRegistry::DataProcessorNode::add<NodeVisualizerDigram>("hex.builtin.nodes.visualizer", "hex.builtin.nodes.visualizer.digram");
|
||||
}
|
||||
|
||||
}
|
@ -48,6 +48,10 @@ namespace hex::plugin::builtin {
|
||||
this->m_dataOverlays.clear();
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventDataChanged>(this, [this] {
|
||||
this->processNodes();
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&, this] {
|
||||
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.load_processor"_lang)) {
|
||||
hex::openFileBrowser("hex.builtin.view.data_processor.menu.file.load_processor"_lang, DialogMode::Open, {
|
||||
@ -71,6 +75,15 @@ namespace hex::plugin::builtin {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ContentRegistry::FileHandler::add({ ".hexnode" }, [this](const auto &path) {
|
||||
File file(path, File::Mode::Read);
|
||||
if (!file.isValid()) return false;
|
||||
|
||||
this->loadNodes(file.readString());
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ViewDataProcessor::~ViewDataProcessor() {
|
||||
@ -81,6 +94,7 @@ namespace hex::plugin::builtin {
|
||||
EventManager::unsubscribe<EventFileLoaded>(this);
|
||||
EventManager::unsubscribe<EventProjectFileStore>(this);
|
||||
EventManager::unsubscribe<EventProjectFileLoad>(this);
|
||||
EventManager::unsubscribe<EventDataChanged>(this);
|
||||
}
|
||||
|
||||
|
||||
@ -129,6 +143,7 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void ViewDataProcessor::processNodes() {
|
||||
|
||||
if (this->m_dataOverlays.size() != this->m_endNodes.size()) {
|
||||
for (auto overlay : this->m_dataOverlays)
|
||||
ImHexApi::Provider::get()->deleteOverlay(overlay);
|
||||
@ -271,60 +286,69 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
if (ImGui::BeginChild("##node_editor", ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 1.3))) {
|
||||
ImNodes::BeginNodeEditor();
|
||||
|
||||
for (auto &node : this->m_nodes) {
|
||||
const bool hasError = this->m_currNodeError.has_value() && this->m_currNodeError->first == node;
|
||||
for (auto &node : this->m_nodes) {
|
||||
const bool hasError = this->m_currNodeError.has_value() && this->m_currNodeError->first == node;
|
||||
|
||||
if (hasError)
|
||||
ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF);
|
||||
if (hasError)
|
||||
ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF);
|
||||
|
||||
ImNodes::BeginNode(node->getId());
|
||||
ImNodes::BeginNode(node->getId());
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle()));
|
||||
ImNodes::EndNodeTitleBar();
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle()));
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
node->drawNode();
|
||||
node->drawNode();
|
||||
|
||||
for (auto &attribute : node->getAttributes()) {
|
||||
ImNodesPinShape pinShape;
|
||||
for (auto &attribute : node->getAttributes()) {
|
||||
ImNodesPinShape pinShape;
|
||||
|
||||
switch (attribute.getType()) {
|
||||
case dp::Attribute::Type::Integer:
|
||||
pinShape = ImNodesPinShape_Circle;
|
||||
break;
|
||||
case dp::Attribute::Type::Float:
|
||||
pinShape = ImNodesPinShape_Triangle;
|
||||
break;
|
||||
case dp::Attribute::Type::Buffer:
|
||||
pinShape = ImNodesPinShape_Quad;
|
||||
break;
|
||||
switch (attribute.getType()) {
|
||||
case dp::Attribute::Type::Integer:
|
||||
pinShape = ImNodesPinShape_Circle;
|
||||
break;
|
||||
case dp::Attribute::Type::Float:
|
||||
pinShape = ImNodesPinShape_Triangle;
|
||||
break;
|
||||
case dp::Attribute::Type::Buffer:
|
||||
pinShape = ImNodesPinShape_Quad;
|
||||
break;
|
||||
}
|
||||
|
||||
if (attribute.getIOType() == dp::Attribute::IOType::In) {
|
||||
ImNodes::BeginInputAttribute(attribute.getId(), pinShape);
|
||||
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
|
||||
ImNodes::EndInputAttribute();
|
||||
} else if (attribute.getIOType() == dp::Attribute::IOType::Out) {
|
||||
ImNodes::BeginOutputAttribute(attribute.getId(), ImNodesPinShape(pinShape + 1));
|
||||
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
|
||||
ImNodes::EndOutputAttribute();
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.getIOType() == dp::Attribute::IOType::In) {
|
||||
ImNodes::BeginInputAttribute(attribute.getId(), pinShape);
|
||||
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
|
||||
ImNodes::EndInputAttribute();
|
||||
} else if (attribute.getIOType() == dp::Attribute::IOType::Out) {
|
||||
ImNodes::BeginOutputAttribute(attribute.getId(), ImNodesPinShape(pinShape + 1));
|
||||
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
|
||||
ImNodes::EndOutputAttribute();
|
||||
}
|
||||
ImNodes::EndNode();
|
||||
|
||||
if (hasError)
|
||||
ImNodes::PopColorStyle();
|
||||
}
|
||||
|
||||
ImNodes::EndNode();
|
||||
for (const auto &link : this->m_links)
|
||||
ImNodes::Link(link.getId(), link.getFromId(), link.getToId());
|
||||
|
||||
if (hasError)
|
||||
ImNodes::PopColorStyle();
|
||||
ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight);
|
||||
|
||||
ImNodes::EndNodeEditor();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
for (const auto &link : this->m_links)
|
||||
ImNodes::Link(link.getId(), link.getFromId(), link.getToId());
|
||||
if (ImGui::IconButton(ICON_VS_DEBUG_START, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen)) || this->m_continuousEvaluation)
|
||||
this->processNodes();
|
||||
|
||||
ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight);
|
||||
|
||||
ImNodes::EndNodeEditor();
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Continuous evaluation", &this->m_continuousEvaluation);
|
||||
|
||||
{
|
||||
int linkId;
|
||||
@ -391,8 +415,6 @@ namespace hex::plugin::builtin {
|
||||
this->eraseNodes(selectedNodes);
|
||||
}
|
||||
}
|
||||
|
||||
this->processNodes();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
@ -440,6 +462,8 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void ViewDataProcessor::loadNodes(const std::string &data) {
|
||||
if (!ImHexApi::Provider::isValid()) return;
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
json input = json::parse(data);
|
||||
@ -536,6 +560,8 @@ namespace hex::plugin::builtin {
|
||||
dp::Node::setIdCounter(maxNodeId + 1);
|
||||
dp::Attribute::setIdCounter(maxAttrId + 1);
|
||||
dp::Link::setIdCounter(maxLinkId + 1);
|
||||
|
||||
this->processNodes();
|
||||
}
|
||||
|
||||
}
|
2
plugins/builtin/source/lang/.clang-format
Normal file
2
plugins/builtin/source/lang/.clang-format
Normal file
@ -0,0 +1,2 @@
|
||||
DisableFormat: true
|
||||
SortIncludes: Never
|
@ -603,6 +603,10 @@ namespace hex::plugin::builtin {
|
||||
{ "hex.builtin.nodes.crypto.aes.mode", "Mode" },
|
||||
{ "hex.builtin.nodes.crypto.aes.key_length", "Key length" },
|
||||
|
||||
{ "hex.builtin.nodes.visualizer", "Visualizers" },
|
||||
{ "hex.builtin.nodes.visualizer.digram", "Digram" },
|
||||
{ "hex.builtin.nodes.visualizer.digram.header", "Digram Visualizer" },
|
||||
{ "hex.builtin.nodes.visualizer.digram.input", "Input" },
|
||||
|
||||
|
||||
{ "hex.builtin.tools.demangler", "Itanium/MSVC demangler" },
|
||||
|
Loading…
Reference in New Issue
Block a user