diff --git a/lib/libimhex/include/hex/data_processor/node.hpp b/lib/libimhex/include/hex/data_processor/node.hpp index 71ff6a1b2..734b23bb0 100644 --- a/lib/libimhex/include/hex/data_processor/node.hpp +++ b/lib/libimhex/include/hex/data_processor/node.hpp @@ -31,7 +31,10 @@ namespace hex::dp { void setUnlocalizedName(const std::string &unlocalizedName) { this->m_unlocalizedName = unlocalizedName; } [[nodiscard]] const std::string &getUnlocalizedTitle() const { return this->m_unlocalizedTitle; } + void setUnlocalizedTitle(std::string title) { this->m_unlocalizedTitle = std::move(title); } + [[nodiscard]] std::vector &getAttributes() { return this->m_attributes; } + [[nodiscard]] const std::vector &getAttributes() const { return this->m_attributes; } void setCurrentOverlay(prv::Overlay *overlay) { this->m_overlay = overlay; @@ -40,8 +43,8 @@ namespace hex::dp { virtual void drawNode() { } virtual void process() = 0; - virtual void store(nlohmann::json &j) { hex::unused(j); } - virtual void load(nlohmann::json &j) { hex::unused(j); } + virtual void store(nlohmann::json &j) const { hex::unused(j); } + virtual void load(const nlohmann::json &j) { hex::unused(j); } struct NodeError { Node *node; @@ -70,6 +73,14 @@ namespace hex::dp { Node::s_idCounter = id; } + std::vector getBufferOnInput(u32 index); + i128 getIntegerOnInput(u32 index); + long double getFloatOnInput(u32 index); + + void setBufferOnOutput(u32 index, const std::vector &data); + void setIntegerOnOutput(u32 index, i128 integer); + void setFloatOnOutput(u32 index, long double floatingPoint); + private: int m_id; std::string m_unlocalizedTitle, m_unlocalizedName; @@ -103,15 +114,14 @@ namespace hex::dp { throw NodeError { this, message }; } - std::vector getBufferOnInput(u32 index); - i128 getIntegerOnInput(u32 index); - long double getFloatOnInput(u32 index); - - void setBufferOnOutput(u32 index, const std::vector &data); - void setIntegerOnOutput(u32 index, i128 integer); - void setFloatOnOutput(u32 index, long double floatingPoint); - void setOverlayData(u64 address, const std::vector &data); + + void setAttributes(std::vector attributes) { + this->m_attributes = std::move(attributes); + + for (auto &attr : this->m_attributes) + attr.setParentNode(this); + } }; } \ No newline at end of file diff --git a/lib/libimhex/include/hex/helpers/fs.hpp b/lib/libimhex/include/hex/helpers/fs.hpp index 19e866058..208c8cb50 100644 --- a/lib/libimhex/include/hex/helpers/fs.hpp +++ b/lib/libimhex/include/hex/helpers/fs.hpp @@ -102,6 +102,7 @@ namespace hex::fs { Inspectors, Themes, Libraries, + Nodes, END }; diff --git a/lib/libimhex/include/hex/helpers/intrinsics.hpp b/lib/libimhex/include/hex/helpers/intrinsics.hpp index b1a49a3fb..106a7f337 100644 --- a/lib/libimhex/include/hex/helpers/intrinsics.hpp +++ b/lib/libimhex/include/hex/helpers/intrinsics.hpp @@ -2,10 +2,6 @@ namespace hex { - [[noreturn]] inline void unreachable() { - __builtin_unreachable(); - } - inline void unused(auto && ... x) { ((void)x, ...); } diff --git a/lib/libimhex/source/helpers/fs.cpp b/lib/libimhex/source/helpers/fs.cpp index bde7e1df6..6cf254c6c 100644 --- a/lib/libimhex/source/helpers/fs.cpp +++ b/lib/libimhex/source/helpers/fs.cpp @@ -101,7 +101,7 @@ namespace hex::fs { result = NFD::PickFolder(outPath, defaultPath.empty() ? nullptr : defaultPath.c_str()); break; default: - hex::unreachable(); + std::unreachable(); } if (result == NFD_OKAY){ @@ -271,6 +271,9 @@ namespace hex::fs { case ImHexPath::Inspectors: result = appendPath(getDefaultPaths(ImHexPath::Scripts), "inspectors"); break; + case ImHexPath::Nodes: + result = appendPath(getDefaultPaths(ImHexPath::Scripts), "nodes"); + break; case ImHexPath::Themes: result = appendPath(getDataPaths(), "themes"); break; diff --git a/plugins/builtin/include/content/helpers/provider_extra_data.hpp b/plugins/builtin/include/content/helpers/provider_extra_data.hpp index 5fb3f3691..e4c97a3f4 100644 --- a/plugins/builtin/include/content/helpers/provider_extra_data.hpp +++ b/plugins/builtin/include/content/helpers/provider_extra_data.hpp @@ -63,12 +63,16 @@ namespace hex::plugin::builtin { std::list bookmarks; struct DataProcessor { - std::list endNodes; - std::list> nodes; - std::list links; + struct Workspace { + std::list> nodes; + std::list endNodes; + std::list links; + std::vector dataOverlays; + std::optional currNodeError; + }; - std::vector dataOverlays; - std::optional currNodeError; + Workspace mainWorkspace; + std::vector workspaceStack; } dataProcessor; struct HexEditor { @@ -101,7 +105,7 @@ namespace hex::plugin::builtin { return get(ImHexApi::Provider::get()); } - static Data& get(hex::prv::Provider *provider) { + static Data& get(const hex::prv::Provider *provider) { return s_data[provider]; } @@ -115,7 +119,7 @@ namespace hex::plugin::builtin { private: ProviderExtraData() = default; - static inline std::map s_data = {}; + static inline std::map s_data = {}; }; } \ No newline at end of file diff --git a/plugins/builtin/include/content/views/view_data_processor.hpp b/plugins/builtin/include/content/views/view_data_processor.hpp index 6c5ae0647..52f8632b9 100644 --- a/plugins/builtin/include/content/views/view_data_processor.hpp +++ b/plugins/builtin/include/content/views/view_data_processor.hpp @@ -7,6 +7,8 @@ #include #include +#include "content/helpers/provider_extra_data.hpp" + #include #include @@ -14,24 +16,38 @@ namespace hex::plugin::builtin { class ViewDataProcessor : public View { public: + using Workspace = ProviderExtraData::Data::DataProcessor::Workspace; + ViewDataProcessor(); ~ViewDataProcessor() override; void drawContent() override; + static nlohmann::json saveNode(const dp::Node *node); + static nlohmann::json saveNodes(const Workspace &workspace); + + static std::unique_ptr loadNode(const nlohmann::json &data); + static void loadNodes(Workspace &workspace, const nlohmann::json &data); + private: - bool m_justSwitchedProvider = false; + static void eraseLink(Workspace &workspace, int id); + static void eraseNodes(Workspace &workspace, const std::vector &ids); + static void processNodes(Workspace &workspace); + + void reloadCustomNodes(); + private: + bool m_updateNodePositions = false; int m_rightClickedId = -1; ImVec2 m_rightClickedCoords; bool m_continuousEvaluation = false; - void eraseLink(int id); - void eraseNodes(const std::vector &ids); - void processNodes(); + struct CustomNode { + std::string name; + nlohmann::json data; + }; - std::string saveNodes(prv::Provider *provider); - void loadNodes(prv::Provider *provider, const std::string &data); + std::vector m_customNodes; }; } \ No newline at end of file diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 76c4114ae..5e4388952 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -268,6 +268,15 @@ "hex.builtin.nodes.crypto.aes.key_length": "Key length", "hex.builtin.nodes.crypto.aes.mode": "Mode", "hex.builtin.nodes.crypto.aes.nonce": "Nonce", + "hex.builtin.nodes.custom": "Custom", + "hex.builtin.nodes.custom.custom": "New Node", + "hex.builtin.nodes.custom.custom.edit": "Edit", + "hex.builtin.nodes.custom.custom.edit_hint": "Hold down SHIFT to edit", + "hex.builtin.nodes.custom.custom.header": "Custom Node", + "hex.builtin.nodes.custom.input": "Custom Node Input", + "hex.builtin.nodes.custom.input.header": "Node Input", + "hex.builtin.nodes.custom.output": "Custom Node Output", + "hex.builtin.nodes.custom.output.header": "Node Output", "hex.builtin.nodes.data_access": "Data access", "hex.builtin.nodes.data_access.read": "Read", "hex.builtin.nodes.data_access.read.address": "Address", @@ -540,6 +549,7 @@ "hex.builtin.view.data_processor.menu.remove_link": "Remove Link", "hex.builtin.view.data_processor.menu.remove_node": "Remove Node", "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.disassembler.16bit": "16-bit", diff --git a/plugins/builtin/source/content/data_processor_nodes.cpp b/plugins/builtin/source/content/data_processor_nodes.cpp index 4d58b0d29..4fefe9fe1 100644 --- a/plugins/builtin/source/content/data_processor_nodes.cpp +++ b/plugins/builtin/source/content/data_processor_nodes.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -18,7 +20,6 @@ #include #include #include -#include namespace hex::plugin::builtin { @@ -50,14 +51,14 @@ namespace hex::plugin::builtin { this->setBufferOnOutput(0, this->m_buffer); } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["size"] = this->m_size; j["data"] = this->m_buffer; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_size = j["size"]; this->m_buffer = j["data"].get>(); } @@ -81,13 +82,13 @@ namespace hex::plugin::builtin { this->setBufferOnOutput(0, hex::decodeByteString(this->m_value)); } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["data"] = this->m_value; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_value = j["data"].get(); } @@ -109,13 +110,13 @@ namespace hex::plugin::builtin { this->setIntegerOnOutput(0, this->m_value); } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["data"] = this->m_value; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_value = j["data"]; } @@ -137,13 +138,13 @@ namespace hex::plugin::builtin { this->setFloatOnOutput(0, this->m_value); } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["data"] = this->m_value; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_value = j["data"]; } @@ -166,13 +167,13 @@ namespace hex::plugin::builtin { } void process() override { - this->setBufferOnOutput(0, hex::toBytes(this->m_color.Value.x * 0xFF)); - this->setBufferOnOutput(1, hex::toBytes(this->m_color.Value.y * 0xFF)); - this->setBufferOnOutput(2, hex::toBytes(this->m_color.Value.z * 0xFF)); - this->setBufferOnOutput(3, hex::toBytes(this->m_color.Value.w * 0xFF)); + this->setBufferOnOutput(0, hex::toBytes(u8(this->m_color.Value.x * 0xFF))); + this->setBufferOnOutput(1, hex::toBytes(u8(this->m_color.Value.y * 0xFF))); + this->setBufferOnOutput(2, hex::toBytes(u8(this->m_color.Value.z * 0xFF))); + this->setBufferOnOutput(3, hex::toBytes(u8(this->m_color.Value.w * 0xFF))); } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["data"] = nlohmann::json::object(); @@ -182,7 +183,7 @@ namespace hex::plugin::builtin { j["data"]["a"] = this->m_color.Value.w; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_color = ImVec4(j["data"]["r"], j["data"]["g"], j["data"]["b"], j["data"]["a"]); } @@ -203,13 +204,13 @@ namespace hex::plugin::builtin { void process() override { } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["comment"] = this->m_comment; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_comment = j["comment"].get(); } @@ -878,7 +879,7 @@ namespace hex::plugin::builtin { this->setBufferOnOutput(4, output); } - void store(nlohmann::json &j) override { + void store(nlohmann::json &j) const override { j = nlohmann::json::object(); j["data"] = nlohmann::json::object(); @@ -886,7 +887,7 @@ namespace hex::plugin::builtin { j["data"]["key_length"] = this->m_keyLength; } - void load(nlohmann::json &j) override { + void load(const nlohmann::json &j) override { this->m_mode = j["data"]["mode"]; this->m_keyLength = j["data"]["key_length"]; } @@ -1122,10 +1123,301 @@ namespace hex::plugin::builtin { } } + void store(nlohmann::json &j) const override { + j = nlohmann::json::object(); + + j["name"] = this->m_name; + } + + void load(const nlohmann::json &j) override { + this->m_name = j["name"].get(); + } + private: std::string m_name; }; + class NodeCustomInput : public dp::Node { + public: + NodeCustomInput() : Node("hex.builtin.nodes.custom.input.header", { dp::Attribute(dp::Attribute::IOType::Out, dp::Attribute::Type::Integer, "hex.builtin.nodes.common.input") }) { } + ~NodeCustomInput() override = default; + + void drawNode() override { + ImGui::PushItemWidth(100_scaled); + if (ImGui::Combo("##type", &this->m_type, "Integer\0Float\0Buffer\0")) { + this->setAttributes({ + { dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.input") } + }); + } + + if (ImGui::InputText("##name", this->m_name)) { + this->setUnlocalizedTitle(this->m_name); + } + + ImGui::PopItemWidth(); + } + + void setValue(auto value) { this->m_value = std::move(value); } + + const std::string &getName() const { return this->m_name; } + dp::Attribute::Type getType() const { + switch (this->m_type) { + default: + case 0: return dp::Attribute::Type::Integer; + case 1: return dp::Attribute::Type::Float; + case 2: return dp::Attribute::Type::Buffer; + } + } + + void process() override { + std::visit(overloaded { + [this](i128 value) { this->setIntegerOnOutput(0, value); }, + [this](long double value) { this->setFloatOnOutput(0, value); }, + [this](const std::vector &value) { this->setBufferOnOutput(0, value); } + }, this->m_value); + } + + void store(nlohmann::json &j) const override { + j = nlohmann::json::object(); + + j["name"] = this->m_name; + j["type"] = this->m_type; + } + + void load(const nlohmann::json &j) override { + this->m_name = j["name"].get(); + this->m_type = j["type"]; + } + + private: + std::string m_name = LangEntry(this->getUnlocalizedName()); + int m_type = 0; + + std::variant> m_value; + }; + + class NodeCustomOutput : public dp::Node { + public: + NodeCustomOutput() : Node("hex.builtin.nodes.custom.output.header", { dp::Attribute(dp::Attribute::IOType::In, dp::Attribute::Type::Integer, "hex.builtin.nodes.common.output") }) { } + ~NodeCustomOutput() override = default; + + void drawNode() override { + ImGui::PushItemWidth(100_scaled); + if (ImGui::Combo("##type", &this->m_type, "Integer\0Float\0Buffer\0")) { + this->setAttributes({ + { dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.output") } + }); + } + + if (ImGui::InputText("##name", this->m_name)) { + this->setUnlocalizedTitle(this->m_name); + } + + ImGui::PopItemWidth(); + } + + const std::string &getName() const { return this->m_name; } + dp::Attribute::Type getType() const { + switch (this->m_type) { + case 0: return dp::Attribute::Type::Integer; + case 1: return dp::Attribute::Type::Float; + case 2: return dp::Attribute::Type::Buffer; + default: return dp::Attribute::Type::Integer; + } + } + + void process() override { + switch (this->getType()) { + case dp::Attribute::Type::Integer: this->m_value = this->getIntegerOnInput(0); break; + case dp::Attribute::Type::Float: this->m_value = this->getFloatOnInput(0); break; + case dp::Attribute::Type::Buffer: this->m_value = this->getBufferOnInput(0); break; + } + } + + const auto& getValue() const { return this->m_value; } + + void store(nlohmann::json &j) const override { + j = nlohmann::json::object(); + + j["name"] = this->m_name; + j["type"] = this->m_type; + } + + void load(const nlohmann::json &j) override { + this->m_name = j["name"].get(); + this->m_type = j["type"]; + } + + private: + std::string m_name = LangEntry(this->getUnlocalizedName()); + int m_type = 0; + + std::variant> m_value; + }; + + class NodeCustom : public dp::Node { + public: + NodeCustom() : Node("hex.builtin.nodes.custom.custom.header", {}) { } + ~NodeCustom() override = default; + + void drawNode() override { + if (this->m_requiresAttributeUpdate) { + this->m_requiresAttributeUpdate = false; + this->setAttributes(this->findAttributes()); + } + + ImGui::PushItemWidth(200_scaled); + + bool editing = false; + if (this->m_editable) { + ImGui::InputTextIcon("##name", ICON_VS_SYMBOL_KEY, this->m_name); + editing = ImGui::IsItemActive(); + + if (ImGui::Button("hex.builtin.nodes.custom.custom.edit"_lang, ImVec2(200_scaled, ImGui::GetTextLineHeightWithSpacing()))) { + ProviderExtraData::getCurrent().dataProcessor.workspaceStack.push_back(&this->m_workspace); + this->m_requiresAttributeUpdate = true; + } + } else { + this->setUnlocalizedTitle(this->m_name); + + if (this->getAttributes().empty()) { + ImGui::TextUnformatted("hex.builtin.nodes.custom.custom.edit_hint"_lang); + } + } + + this->m_editable = ImGui::GetIO().KeyShift || editing; + + ImGui::PopItemWidth(); + } + + void process() override { + auto indexFromId = [this](u32 id) -> std::optional { + const auto &attributes = this->getAttributes(); + for (u32 i = 0; i < attributes.size(); i++) + if (u32(attributes[i].getId()) == id) + return i; + return std::nullopt; + }; + + // Forward inputs to input nodes values + for (auto &attribute : this->getAttributes()) { + auto index = indexFromId(attribute.getId()); + if (!index.has_value()) + continue; + + if (auto input = this->findInput(attribute.getUnlocalizedName()); input != nullptr) { + switch (attribute.getType()) { + case dp::Attribute::Type::Integer: { + auto value = this->getIntegerOnInput(*index); + input->setValue(value); + break; + } + case dp::Attribute::Type::Float: { + auto value = this->getFloatOnInput(*index); + input->setValue(value); + break; + } + case dp::Attribute::Type::Buffer: { + auto value = this->getBufferOnInput(*index); + input->setValue(value); + break; + } + } + } + } + + // Process all nodes in our workspace + for (auto &endNode : this->m_workspace.endNodes) { + endNode->resetOutputData(); + + for (auto &node : this->m_workspace.nodes) + node->resetProcessedInputs(); + + endNode->process(); + } + + // Forward output node values to outputs + for (auto &attribute : this->getAttributes()) { + auto index = indexFromId(attribute.getId()); + if (!index.has_value()) + continue; + + if (auto output = this->findOutput(attribute.getUnlocalizedName()); output != nullptr) { + switch (attribute.getType()) { + case dp::Attribute::Type::Integer: { + auto value = std::get(output->getValue()); + this->setIntegerOnOutput(*index, value); + break; + } + case dp::Attribute::Type::Float: { + auto value = std::get(output->getValue()); + this->setFloatOnOutput(*index, value); + break; + } + case dp::Attribute::Type::Buffer: { + auto value = std::get>(output->getValue()); + this->setBufferOnOutput(*index, value); + break; + } + } + } + } + } + + void store(nlohmann::json &j) const override { + j = nlohmann::json::object(); + + j["nodes"] = ViewDataProcessor::saveNodes(this->m_workspace); + } + + void load(const nlohmann::json &j) override { + ViewDataProcessor::loadNodes(this->m_workspace, j["nodes"]); + + this->m_name = LangEntry(this->getUnlocalizedTitle()).get(); + this->m_requiresAttributeUpdate = true; + } + + private: + std::vector findAttributes() { + std::vector result; + + for (auto &node : this->m_workspace.nodes) { + if (auto *inputNode = dynamic_cast(node.get()); inputNode != nullptr) + result.emplace_back(dp::Attribute::IOType::In, inputNode->getType(), inputNode->getName()); + else if (auto *outputNode = dynamic_cast(node.get()); outputNode != nullptr) + result.emplace_back(dp::Attribute::IOType::Out, outputNode->getType(), outputNode->getName()); + } + + return result; + } + + NodeCustomInput* findInput(const std::string &name) { + for (auto &node : this->m_workspace.nodes) { + if (auto *inputNode = dynamic_cast(node.get()); inputNode != nullptr && inputNode->getName() == name) + return inputNode; + } + + return nullptr; + } + + NodeCustomOutput* findOutput(const std::string &name) { + for (auto &node : this->m_workspace.nodes) { + if (auto *outputNode = dynamic_cast(node.get()); outputNode != nullptr && outputNode->getName() == name) + return outputNode; + } + + return nullptr; + } + + private: + std::string m_name = "hex.builtin.nodes.custom.custom.header"_lang; + + bool m_editable = false; + + bool m_requiresAttributeUpdate = false; + ProviderExtraData::Data::DataProcessor::Workspace m_workspace; + }; + 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"); @@ -1188,6 +1480,10 @@ namespace hex::plugin::builtin { ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.visualizer", "hex.builtin.nodes.visualizer.byte_distribution"); ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.pattern_language", "hex.builtin.nodes.pattern_language.out_var"); + + ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.custom", "hex.builtin.nodes.custom.custom"); + ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.custom", "hex.builtin.nodes.custom.input"); + ContentRegistry::DataProcessorNode::add("hex.builtin.nodes.custom", "hex.builtin.nodes.custom.output"); } } \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_about.cpp b/plugins/builtin/source/content/views/view_about.cpp index 4da6ad8e6..2baa0d1c7 100644 --- a/plugins/builtin/source/content/views/view_about.cpp +++ b/plugins/builtin/source/content/views/view_about.cpp @@ -159,6 +159,7 @@ namespace hex::plugin::builtin { { "Scripts", fs::ImHexPath::Scripts }, { "Themes", fs::ImHexPath::Themes }, { "Data inspector scripts", fs::ImHexPath::Inspectors }, + { "Custom data processor nodes", fs::ImHexPath::Nodes }, } }; diff --git a/plugins/builtin/source/content/views/view_data_processor.cpp b/plugins/builtin/source/content/views/view_data_processor.cpp index a60592a06..02c64f790 100644 --- a/plugins/builtin/source/content/views/view_data_processor.cpp +++ b/plugins/builtin/source/content/views/view_data_processor.cpp @@ -3,11 +3,13 @@ #include #include +#include #include #include #include +#include #include #include @@ -20,52 +22,71 @@ namespace hex::plugin::builtin { .required = false, .load = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) { auto save = tar.readString(basePath); + auto &data = ProviderExtraData::get(provider).dataProcessor; - this->loadNodes(provider, save); + ViewDataProcessor::loadNodes(data.mainWorkspace, save); + this->m_updateNodePositions = true; return true; }, - .store = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) { - tar.write(basePath, this->saveNodes(provider)); + .store = [](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) { + auto &data = ProviderExtraData::get(provider).dataProcessor; + + tar.write(basePath, ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4)); return true; } }); + EventManager::subscribe(this, [](const auto *provider) { + auto &data = ProviderExtraData::get(provider).dataProcessor; + + data.mainWorkspace = { }; + data.workspaceStack.push_back(&data.mainWorkspace); + }); + EventManager::subscribe(this, [this](const auto &, const auto &) { auto &data = ProviderExtraData::getCurrent().dataProcessor; - for (auto &node : data.nodes) { - node->setCurrentOverlay(nullptr); + + for (auto *workspace : data.workspaceStack) { + for (auto &node : workspace->nodes) { + node->setCurrentOverlay(nullptr); + } + + workspace->dataOverlays.clear(); } - data.dataOverlays.clear(); - this->m_justSwitchedProvider = true; + + this->m_updateNodePositions = true; }); - EventManager::subscribe(this, [this] { - this->processNodes(); + EventManager::subscribe(this, [] { + auto &workspace = *ProviderExtraData::getCurrent().dataProcessor.workspaceStack.back(); + + ViewDataProcessor::processNodes(workspace); }); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&, this] { + ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&] { bool providerValid = ImHexApi::Provider::isValid(); - auto provider = ImHexApi::Provider::get(); auto &data = ProviderExtraData::getCurrent().dataProcessor; if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.load_processor"_lang, nullptr, false, providerValid)) { - fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode"} }, - [&, this](const std::fs::path &path) { + fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, + [&](const std::fs::path &path) { fs::File file(path, fs::File::Mode::Read); - if (file.isValid()) - this->loadNodes(provider, file.readString()); + if (file.isValid()) { + ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString()); + this->m_updateNodePositions = true; + } }); } - if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.save_processor"_lang, nullptr, false, !data.nodes.empty() && providerValid)) { - fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode"} }, - [&, this](const std::fs::path &path) { + if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.save_processor"_lang, nullptr, false, !data.workspaceStack.empty() && !data.workspaceStack.back()->nodes.empty() && providerValid)) { + fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, + [&](const std::fs::path &path) { fs::File file(path, fs::File::Mode::Create); if (file.isValid()) - file.write(this->saveNodes(provider)); + file.write(ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4)); }); } }); @@ -74,42 +95,44 @@ namespace hex::plugin::builtin { fs::File file(path, fs::File::Mode::Read); if (!file.isValid()) return false; - this->loadNodes(ImHexApi::Provider::get(), file.readString()); + auto &data = ProviderExtraData::getCurrent().dataProcessor; + + ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString()); + this->m_updateNodePositions = true; return true; }); } ViewDataProcessor::~ViewDataProcessor() { + EventManager::unsubscribe(this); + EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); } - void ViewDataProcessor::eraseLink(int id) { - auto &data = ProviderExtraData::getCurrent().dataProcessor; + void ViewDataProcessor::eraseLink(ProviderExtraData::Data::DataProcessor::Workspace &workspace, int id) { + auto link = std::find_if(workspace.links.begin(), workspace.links.end(), [&id](auto link) { return link.getId() == id; }); - auto link = std::find_if(data.links.begin(), data.links.end(), [&id](auto link) { return link.getId() == id; }); - - if (link == data.links.end()) + if (link == workspace.links.end()) return; - for (auto &node : data.nodes) { + for (auto &node : workspace.nodes) { for (auto &attribute : node->getAttributes()) { attribute.removeConnectedAttribute(id); } } - data.links.erase(link); + workspace.links.erase(link); ImHexApi::Provider::markDirty(); } - void ViewDataProcessor::eraseNodes(const std::vector &ids) { - auto &data = ProviderExtraData::getCurrent().dataProcessor; + void ViewDataProcessor::eraseNodes(ProviderExtraData::Data::DataProcessor::Workspace &workspace, const std::vector &ids) { for (int id : ids) { - auto node = std::find_if(data.nodes.begin(), data.nodes.end(), + auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(), [&id](const auto &node) { return node->getId() == id; }); @@ -120,64 +143,81 @@ namespace hex::plugin::builtin { linksToRemove.push_back(linkId); for (auto linkId : linksToRemove) - eraseLink(linkId); + eraseLink(workspace, linkId); } } for (int id : ids) { - auto node = std::find_if(data.nodes.begin(), data.nodes.end(), [&id](const auto &node) { return node->getId() == id; }); + auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(), [&id](const auto &node) { return node->getId() == id; }); - std::erase_if(data.endNodes, [&id](const auto &node) { return node->getId() == id; }); + std::erase_if(workspace.endNodes, [&id](const auto &node) { return node->getId() == id; }); - data.nodes.erase(node); + workspace.nodes.erase(node); } ImHexApi::Provider::markDirty(); } - void ViewDataProcessor::processNodes() { - auto &data = ProviderExtraData::getCurrent().dataProcessor; - - if (data.dataOverlays.size() != data.endNodes.size()) { - for (auto overlay : data.dataOverlays) + void ViewDataProcessor::processNodes(ProviderExtraData::Data::DataProcessor::Workspace &workspace) { + if (workspace.dataOverlays.size() != workspace.endNodes.size()) { + for (auto overlay : workspace.dataOverlays) ImHexApi::Provider::get()->deleteOverlay(overlay); - data.dataOverlays.clear(); + workspace.dataOverlays.clear(); - for (u32 i = 0; i < data.endNodes.size(); i++) - data.dataOverlays.push_back(ImHexApi::Provider::get()->newOverlay()); + for (u32 i = 0; i < workspace.endNodes.size(); i++) + workspace.dataOverlays.push_back(ImHexApi::Provider::get()->newOverlay()); } u32 overlayIndex = 0; - for (auto endNode : data.endNodes) { - endNode->setCurrentOverlay(data.dataOverlays[overlayIndex]); + for (auto endNode : workspace.endNodes) { + endNode->setCurrentOverlay(workspace.dataOverlays[overlayIndex]); overlayIndex++; } - data.currNodeError.reset(); + workspace.currNodeError.reset(); try { - for (auto &endNode : data.endNodes) { + for (auto &endNode : workspace.endNodes) { endNode->resetOutputData(); - for (auto &node : data.nodes) + for (auto &node : workspace.nodes) node->resetProcessedInputs(); endNode->process(); } } catch (dp::Node::NodeError &e) { - data.currNodeError = e; + workspace.currNodeError = e; - for (auto overlay : data.dataOverlays) + for (auto overlay : workspace.dataOverlays) ImHexApi::Provider::get()->deleteOverlay(overlay); - data.dataOverlays.clear(); + workspace.dataOverlays.clear(); } catch (std::runtime_error &e) { - printf("Node implementation bug! %s\n", e.what()); + log::fatal("Node implementation bug! {}\n", e.what()); + } + } + + void ViewDataProcessor::reloadCustomNodes() { + this->m_customNodes.clear(); + + for (const auto &basePath : fs::getDefaultPaths(fs::ImHexPath::Nodes)) { + for (const auto &entry : std::fs::recursive_directory_iterator(basePath)) { + if (entry.path().extension() == ".hexnode") { + try { + nlohmann::json nodeJson = nlohmann::json::parse(fs::File(entry.path(), fs::File::Mode::Read).readString()); + + this->m_customNodes.push_back(CustomNode { LangEntry(nodeJson["name"]), nodeJson }); + } catch (nlohmann::json::exception &e) { + continue; + } + } + } } } void ViewDataProcessor::drawContent() { auto &data = ProviderExtraData::getCurrent().dataProcessor; + auto &workspace = *data.workspaceStack.back(); if (ImGui::Begin(View::toWindowName("hex.builtin.view.data_processor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { @@ -191,8 +231,10 @@ namespace hex::plugin::builtin { ImGui::OpenPopup("Node Menu"); else if (ImNodes::IsLinkHovered(&this->m_rightClickedId)) ImGui::OpenPopup("Link Menu"); - else + else { ImGui::OpenPopup("Context Menu"); + this->reloadCustomNodes(); + } } if (ImGui::BeginPopup("Context Menu")) { @@ -204,14 +246,14 @@ namespace hex::plugin::builtin { ids.resize(ImNodes::NumSelectedNodes()); ImNodes::GetSelectedNodes(ids.data()); - this->eraseNodes(ids); + this->eraseNodes(workspace, ids); ImNodes::ClearNodeSelection(); ids.resize(ImNodes::NumSelectedLinks()); ImNodes::GetSelectedLinks(ids.data()); for (auto id : ids) - this->eraseLink(id); + this->eraseLink(workspace, id); ImNodes::ClearLinkSelection(); } } @@ -233,6 +275,17 @@ namespace hex::plugin::builtin { } } + if (ImGui::BeginMenu("hex.builtin.nodes.custom"_lang)) { + ImGui::Separator(); + + for (auto &customNode : this->m_customNodes) { + if (ImGui::MenuItem(customNode.name.c_str())) { + node = loadNode(customNode.data); + } + } + ImGui::EndMenu(); + } + if (node != nullptr) { bool hasOutput = false; bool hasInput = false; @@ -245,10 +298,10 @@ namespace hex::plugin::builtin { } if (hasInput && !hasOutput) - data.endNodes.push_back(node.get()); + workspace.endNodes.push_back(node.get()); ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords); - data.nodes.push_back(std::move(node)); + workspace.nodes.push_back(std::move(node)); ImHexApi::Provider::markDirty(); } @@ -256,44 +309,64 @@ namespace hex::plugin::builtin { } if (ImGui::BeginPopup("Node Menu")) { + if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.save_node"_lang)) { + auto it = std::find_if(workspace.nodes.begin(), workspace.nodes.end(), + [this](const auto &node) { + return node->getId() == this->m_rightClickedId; + }); + + if (it != workspace.nodes.end()) { + auto &node = *it; + fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, [&](const std::fs::path &path){ + fs::File outputFile(path, fs::File::Mode::Create); + outputFile.write(ViewDataProcessor::saveNode(node.get()).dump(4)); + }); + } + } + + ImGui::Separator(); + if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_node"_lang)) - this->eraseNodes({ this->m_rightClickedId }); + this->eraseNodes(workspace, { this->m_rightClickedId }); ImGui::EndPopup(); } if (ImGui::BeginPopup("Link Menu")) { if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_link"_lang)) - this->eraseLink(this->m_rightClickedId); + this->eraseLink(workspace, this->m_rightClickedId); ImGui::EndPopup(); } { int nodeId; - if (ImNodes::IsNodeHovered(&nodeId) && data.currNodeError.has_value() && data.currNodeError->node->getId() == nodeId) { + if (ImNodes::IsNodeHovered(&nodeId) && workspace.currNodeError.has_value() && workspace.currNodeError->node->getId() == nodeId) { ImGui::BeginTooltip(); ImGui::TextUnformatted("hex.builtin.common.error"_lang); ImGui::Separator(); - ImGui::TextUnformatted(data.currNodeError->message.c_str()); + ImGui::TextUnformatted(workspace.currNodeError->message.c_str()); ImGui::EndTooltip(); } } - if (ImGui::BeginChild("##node_editor", ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 1.3))) { + if (ImGui::BeginChild("##node_editor", ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 1.3F))) { ImNodes::BeginNodeEditor(); - for (auto &node : data.nodes) { - const bool hasError = data.currNodeError.has_value() && data.currNodeError->node == node.get(); + for (auto &node : workspace.nodes) { + const bool hasError = workspace.currNodeError.has_value() && workspace.currNodeError->node == node.get(); if (hasError) ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF); int nodeId = node->getId(); - if (!this->m_justSwitchedProvider) - node->setPosition(ImNodes::GetNodeGridSpacePos(nodeId)); - else + if (this->m_updateNodePositions) { + this->m_updateNodePositions = false; ImNodes::SetNodeGridSpacePos(nodeId, node->getPosition()); + } else { + if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Nodes, nodeId) >= 0) + node->setPosition(ImNodes::GetNodeGridSpacePos(nodeId)); + } ImNodes::BeginNode(nodeId); @@ -301,7 +374,9 @@ namespace hex::plugin::builtin { ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle())); ImNodes::EndNodeTitleBar(); + ImGui::PopStyleVar(); node->drawNode(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0F, 1.0F)); for (auto &attribute : node->getAttributes()) { ImNodesPinShape pinShape; @@ -338,28 +413,50 @@ namespace hex::plugin::builtin { ImNodes::PopColorStyle(); } - for (const auto &link : data.links) + std::vector linksToRemove; + for (const auto &link : workspace.links) { + if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getFromId()) == -1 || + ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getToId()) == -1) { + + linksToRemove.push_back(link.getId()); + } + } + for (auto linkId : linksToRemove) + this->eraseLink(workspace, linkId); + + for (const auto &link : workspace.links) { ImNodes::Link(link.getId(), link.getFromId(), link.getToId()); + } ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight); - if (data.nodes.empty()) + if (workspace.nodes.empty()) ImGui::TextFormattedCentered("{}", "hex.builtin.view.data_processor.help_text"_lang); + if (data.workspaceStack.size() > 1) { + ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeightWithSpacing() * 1.2F, ImGui::GetTextLineHeightWithSpacing() * 0.2F)); + if (ImGui::IconButton(ICON_VS_CLOSE, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGray))) { + data.workspaceStack.pop_back(); + this->m_updateNodePositions = true; + } + } + ImNodes::EndNodeEditor(); } ImGui::EndChild(); if (ImGui::IconButton(ICON_VS_DEBUG_START, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen)) || this->m_continuousEvaluation) - this->processNodes(); + this->processNodes(workspace); ImGui::SameLine(); + ImGui::Checkbox("Continuous evaluation", &this->m_continuousEvaluation); + { int linkId; if (ImNodes::IsLinkDestroyed(&linkId)) { - this->eraseLink(linkId); + this->eraseLink(workspace, linkId); } } @@ -369,7 +466,7 @@ namespace hex::plugin::builtin { do { dp::Attribute *fromAttr = nullptr, *toAttr = nullptr; - for (auto &node : data.nodes) { + for (auto &node : workspace.nodes) { for (auto &attribute : node->getAttributes()) { if (attribute.getId() == from) fromAttr = &attribute; @@ -390,7 +487,7 @@ namespace hex::plugin::builtin { if (!toAttr->getConnectedAttributes().empty()) break; - auto newLink = data.links.emplace_back(from, to); + auto newLink = workspace.links.emplace_back(from, to); fromAttr->addConnectedAttribute(newLink.getId(), toAttr); toAttr->addConnectedAttribute(newLink.getId(), fromAttr); @@ -406,7 +503,7 @@ namespace hex::plugin::builtin { ImNodes::GetSelectedLinks(selectedLinks.data()); for (const int id : selectedLinks) { - eraseLink(id); + eraseLink(workspace, id); } } } @@ -418,48 +515,52 @@ namespace hex::plugin::builtin { selectedNodes.resize(static_cast(selectedNodeCount)); ImNodes::GetSelectedNodes(selectedNodes.data()); - this->eraseNodes(selectedNodes); + this->eraseNodes(workspace, selectedNodes); } } - - this->m_justSwitchedProvider = false; } ImGui::End(); } - std::string ViewDataProcessor::saveNodes(prv::Provider *provider) { - auto &data = ProviderExtraData::get(provider).dataProcessor; + nlohmann::json ViewDataProcessor::saveNode(const dp::Node *node) { + nlohmann::json output; - using json = nlohmann::json; - json output; + output["name"] = node->getUnlocalizedTitle(); + output["type"] = node->getUnlocalizedName(); - output["nodes"] = json::object(); - for (auto &node : data.nodes) { - auto id = node->getId(); - auto &currNodeOutput = output["nodes"][std::to_string(id)]; - auto pos = node->getPosition(); + nlohmann::json nodeData; + node->store(nodeData); + output["data"] = nodeData; - currNodeOutput["type"] = node->getUnlocalizedName(); - currNodeOutput["pos"] = { - {"x", pos.x}, - { "y", pos.y} - }; - currNodeOutput["attrs"] = json::array(); - currNodeOutput["id"] = id; - - json nodeData; - node->store(nodeData); - currNodeOutput["data"] = nodeData; - - u32 attrIndex = 0; - for (auto &attr : node->getAttributes()) { - currNodeOutput["attrs"][attrIndex] = attr.getId(); - attrIndex++; - } + output["attrs"] = nlohmann::json::array(); + u32 attrIndex = 0; + for (auto &attr : node->getAttributes()) { + output["attrs"][attrIndex] = attr.getId(); + attrIndex++; } - output["links"] = json::object(); - for (auto &link : data.links) { + return output; + } + + nlohmann::json ViewDataProcessor::saveNodes(const ViewDataProcessor::Workspace &workspace) { + nlohmann::json output; + + output["nodes"] = nlohmann::json::object(); + for (auto &node : workspace.nodes) { + auto id = node->getId(); + auto &currNodeOutput = output["nodes"][std::to_string(id)]; + auto pos = node->getPosition(); + + currNodeOutput = saveNode(node.get()); + currNodeOutput["id"] = id; + currNodeOutput["pos"] = { + { "x", pos.x }, + { "y", pos.y } + }; + } + + output["links"] = nlohmann::json::object(); + for (auto &link : workspace.links) { auto id = link.getId(); auto &currOutput = output["links"][std::to_string(id)]; @@ -468,41 +569,55 @@ namespace hex::plugin::builtin { currOutput["to"] = link.getToId(); } - return output.dump(4); + return output; } - void ViewDataProcessor::loadNodes(prv::Provider *provider, const std::string &jsonData) { - if (!ImHexApi::Provider::isValid()) return; + std::unique_ptr ViewDataProcessor::loadNode(const nlohmann::json &node) { + try { - auto &data = ProviderExtraData::get(provider).dataProcessor; + auto &nodeEntries = ContentRegistry::DataProcessorNode::getEntries(); - using json = nlohmann::json; - - json input = json::parse(jsonData); - - u32 maxNodeId = 0; - u32 maxAttrId = 0; - u32 maxLinkId = 0; - - data.nodes.clear(); - data.endNodes.clear(); - data.links.clear(); - - auto &nodeEntries = ContentRegistry::DataProcessorNode::getEntries(); - for (auto &node : input["nodes"]) { std::unique_ptr newNode; for (auto &entry : nodeEntries) { if (entry.name == node["type"].get()) newNode = entry.creatorFunction(); } + if (newNode == nullptr) + return nullptr; + + if (node.contains("id")) + newNode->setId(node["id"]); + + if (node.contains("name")) + newNode->setUnlocalizedTitle(node["name"]); + + u32 attrIndex = 0; + for (auto &attr : newNode->getAttributes()) { + attr.setId(node["attrs"][attrIndex]); + attrIndex++; + } + + if (!node["data"].is_null()) + newNode->load(node["data"]); + + return newNode; + } catch (nlohmann::json::exception &e) { + return nullptr; + } + } + + void ViewDataProcessor::loadNodes(ViewDataProcessor::Workspace &workspace, const nlohmann::json &jsonData) { + workspace.nodes.clear(); + workspace.endNodes.clear(); + workspace.links.clear(); + + for (auto &node : jsonData["nodes"]) { + auto newNode = loadNode(node); if (newNode == nullptr) continue; - u32 nodeId = node["id"]; - maxNodeId = std::max(nodeId, maxNodeId); - - newNode->setId(nodeId); + newNode->setPosition(ImVec2(node["pos"]["x"], node["pos"]["y"])); bool hasOutput = false; bool hasInput = false; @@ -514,10 +629,7 @@ namespace hex::plugin::builtin { if (attr.getIOType() == dp::Attribute::IOType::In) hasInput = true; - u32 attrId = node["attrs"][attrIndex]; - maxAttrId = std::max(attrId, maxAttrId); - - attr.setId(attrId); + attr.setId(node["attrs"][attrIndex]); attrIndex++; } @@ -525,23 +637,23 @@ namespace hex::plugin::builtin { newNode->load(node["data"]); if (hasInput && !hasOutput) - data.endNodes.push_back(newNode.get()); + workspace.endNodes.push_back(newNode.get()); - data.nodes.push_back(std::move(newNode)); - ImNodes::SetNodeGridSpacePos(nodeId, ImVec2(node["pos"]["x"], node["pos"]["y"])); + workspace.nodes.push_back(std::move(newNode)); } - for (auto &link : input["links"]) { + int maxLinkId = 0; + for (auto &link : jsonData["links"]) { dp::Link newLink(link["from"], link["to"]); - u32 linkId = link["id"]; + int linkId = link["id"]; maxLinkId = std::max(linkId, maxLinkId); newLink.setID(linkId); - data.links.push_back(newLink); + workspace.links.push_back(newLink); dp::Attribute *fromAttr = nullptr, *toAttr = nullptr; - for (auto &node : data.nodes) { + for (auto &node : workspace.nodes) { for (auto &attribute : node->getAttributes()) { if (attribute.getId() == newLink.getFromId()) fromAttr = &attribute; @@ -566,11 +678,21 @@ namespace hex::plugin::builtin { toAttr->addConnectedAttribute(newLink.getId(), fromAttr); } + int maxNodeId = 0; + int maxAttrId = 0; + for (auto &node : workspace.nodes) { + maxNodeId = std::max(maxNodeId, node->getId()); + + for (auto &attr : node->getAttributes()) { + maxAttrId = std::max(maxAttrId, attr.getId()); + } + } + dp::Node::setIdCounter(maxNodeId + 1); dp::Attribute::setIdCounter(maxAttrId + 1); dp::Link::setIdCounter(maxLinkId + 1); - this->processNodes(); + ViewDataProcessor::processNodes(workspace); } } \ No newline at end of file diff --git a/plugins/windows/source/content/ui_items.cpp b/plugins/windows/source/content/ui_items.cpp index e1762803e..ea41888e4 100644 --- a/plugins/windows/source/content/ui_items.cpp +++ b/plugins/windows/source/content/ui_items.cpp @@ -20,10 +20,12 @@ namespace hex::plugin::windows { // Explicitly trigger a segfault by writing to an invalid memory location // Used for debugging crashes *reinterpret_cast(0x10) = 0x10; + std::unreachable(); } else if (ImGui::GetIO().KeyShift) { // Explicitly trigger an abort by throwing an uncaught exception // Used for debugging exception errors throw std::runtime_error("Debug Error"); + std::unreachable(); } else { hex::openWebpage("https://imhex.werwolv.net/debug"); }