diff --git a/main/source/init/splash_window.cpp b/main/source/init/splash_window.cpp index 6e6c437e0..460498981 100644 --- a/main/source/init/splash_window.cpp +++ b/main/source/init/splash_window.cpp @@ -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 diff --git a/plugins/builtin/include/content/views/view_data_processor.hpp b/plugins/builtin/include/content/views/view_data_processor.hpp index 927df86c0..ae15e615f 100644 --- a/plugins/builtin/include/content/views/view_data_processor.hpp +++ b/plugins/builtin/include/content/views/view_data_processor.hpp @@ -35,6 +35,8 @@ namespace hex::plugin::builtin { std::optional m_currNodeError; + bool m_continuousEvaluation = false; + void eraseLink(u32 id); void eraseNodes(const std::vector &ids); void processNodes(); diff --git a/plugins/builtin/source/content/data_processor_nodes.cpp b/plugins/builtin/source/content/data_processor_nodes.cpp index 3d1913d84..cd5062d6a 100644 --- a/plugins/builtin/source/content/data_processor_nodes.cpp +++ b/plugins/builtin/source/content/data_processor_nodes.cpp @@ -1,18 +1,20 @@ #include #include +#include #include #include -#include - +#include #include #include +#include #include #include #include +#include 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> orderedData; + for (u32 i = 0; i < SequenceCount; i++) { + ssize_t offset = random() % buffer.size(); + + std::vector sequence; + sequence.reserve(SampleSize); + std::copy(buffer.begin() + offset, buffer.begin() + offset + std::min(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 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 m_buffer; + std::vector m_opacityBuffer; + size_t m_highestCount = 0; + }; + void registerDataProcessorNodes() { ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.constants", "hex.builtin.nodes.constants.int"); ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.constants", "hex.builtin.nodes.constants.float"); @@ -797,6 +882,8 @@ namespace hex::plugin::builtin { ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.decoding", "hex.builtin.nodes.decoding.hex"); ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.crypto", "hex.builtin.nodes.crypto.aes"); + + ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.visualizer", "hex.builtin.nodes.visualizer.digram"); } } \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_data_processor.cpp b/plugins/builtin/source/content/views/view_data_processor.cpp index aaf8a567a..9a7f6bc39 100644 --- a/plugins/builtin/source/content/views/view_data_processor.cpp +++ b/plugins/builtin/source/content/views/view_data_processor.cpp @@ -48,6 +48,10 @@ namespace hex::plugin::builtin { this->m_dataOverlays.clear(); }); + EventManager::subscribe(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(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); + EventManager::unsubscribe(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(); } } \ No newline at end of file diff --git a/plugins/builtin/source/lang/.clang-format b/plugins/builtin/source/lang/.clang-format new file mode 100644 index 000000000..15f9b6fc9 --- /dev/null +++ b/plugins/builtin/source/lang/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file diff --git a/plugins/builtin/source/lang/en_US.cpp b/plugins/builtin/source/lang/en_US.cpp index 75f352ef9..f6caf6528 100644 --- a/plugins/builtin/source/lang/en_US.cpp +++ b/plugins/builtin/source/lang/en_US.cpp @@ -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" },