diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index 3e5a4d941..a0624732c 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -1000,6 +1000,24 @@ namespace hex { [[nodiscard]] bool isExperimentEnabled(const std::string &experimentName); } + + namespace Reports { + + namespace impl { + + using Callback = std::function; + + struct ReportGenerator { + Callback callback; + }; + + std::vector& getGenerators(); + + } + + void addReportProvider(impl::Callback callback); + + } } } diff --git a/lib/libimhex/include/hex/helpers/utils.hpp b/lib/libimhex/include/hex/helpers/utils.hpp index 7e8dfcb7b..f5bd8b4fa 100644 --- a/lib/libimhex/include/hex/helpers/utils.hpp +++ b/lib/libimhex/include/hex/helpers/utils.hpp @@ -22,6 +22,10 @@ struct ImVec2; namespace hex { + namespace prv { + class Provider; + } + template [[nodiscard]] std::vector sampleData(const std::vector &data, size_t count) { size_t stride = std::max(1.0, double(data.size()) / count); @@ -286,4 +290,7 @@ namespace hex { [[nodiscard]] std::optional getInitialFilePath(); + [[nodiscard]] std::string generateHexView(u64 offset, u64 size, prv::Provider *provider); + [[nodiscard]] std::string generateHexView(u64 offset, const std::vector &data); + } diff --git a/lib/libimhex/include/hex/providers/provider.hpp b/lib/libimhex/include/hex/providers/provider.hpp index 8bcec9ae2..ad25fd0b0 100644 --- a/lib/libimhex/include/hex/providers/provider.hpp +++ b/lib/libimhex/include/hex/providers/provider.hpp @@ -172,7 +172,7 @@ namespace hex::prv { [[nodiscard]] Overlay *newOverlay(); void deleteOverlay(Overlay *overlay); - [[nodiscard]] const std::list> &getOverlays(); + [[nodiscard]] const std::list> &getOverlays() const; [[nodiscard]] size_t getPageSize() const; void setPageSize(size_t pageSize); diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 8f69889a9..750b4cd8f 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -1098,4 +1098,22 @@ namespace hex { } + namespace ContentRegistry::Reports { + + namespace impl { + + std::vector &getGenerators() { + static std::vector generators; + + return generators; + } + + } + + void addReportProvider(impl::Callback callback) { + impl::getGenerators().push_back(impl::ReportGenerator { std::move(callback ) }); + } + + } + } diff --git a/lib/libimhex/source/helpers/utils.cpp b/lib/libimhex/source/helpers/utils.cpp index 9ee4f9e71..73dffa8f9 100644 --- a/lib/libimhex/source/helpers/utils.cpp +++ b/lib/libimhex/source/helpers/utils.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include @@ -552,4 +554,70 @@ namespace hex { return fileToOpen; } + namespace { + + std::string generateHexViewImpl(u64 offset, auto begin, auto end) { + constexpr static auto HeaderLine = "Hex View 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"; + std::string result; + + const auto size = std::distance(begin, end); + result.reserve(std::string(HeaderLine).size() * size / 0x10); + + result += HeaderLine; + + u64 address = offset & ~u64(0x0F); + std::string asciiRow; + for (auto it = begin; it != end; ++it) { + u8 byte = *it; + + if ((address % 0x10) == 0) { + result += hex::format(" {}", asciiRow); + result += hex::format("\n{0:08X} ", address); + + asciiRow.clear(); + + if (address == (offset & ~u64(0x0F))) { + for (u64 i = 0; i < (offset - address); i++) { + result += " "; + asciiRow += " "; + } + + if (offset - address >= 8) + result += " "; + + address = offset; + } + } + + result += hex::format("{0:02X} ", byte); + asciiRow += std::isprint(byte) ? char(byte) : '.'; + if ((address % 0x10) == 0x07) + result += " "; + + address++; + } + + if ((address % 0x10) != 0x00) + for (u32 i = 0; i < (0x10 - (address % 0x10)); i++) + result += " "; + + result += hex::format(" {}", asciiRow); + + return result; + } + + } + + std::string generateHexView(u64 offset, u64 size, prv::Provider *provider) { + auto reader = prv::ProviderReader(provider); + reader.seek(offset); + reader.setEndAddress((offset + size) - 1); + + return generateHexViewImpl(offset, reader.begin(), reader.end()); + } + + std::string generateHexView(u64 offset, const std::vector &data) { + return generateHexViewImpl(offset, data.begin(), data.end()); + } + } \ No newline at end of file diff --git a/lib/libimhex/source/providers/provider.cpp b/lib/libimhex/source/providers/provider.cpp index fc42e7a54..021a1b340 100644 --- a/lib/libimhex/source/providers/provider.cpp +++ b/lib/libimhex/source/providers/provider.cpp @@ -173,7 +173,7 @@ namespace hex::prv { }); } - const std::list> &Provider::getOverlays() { + const std::list> &Provider::getOverlays() const { return this->m_overlays; } diff --git a/main/gui/source/init/tasks.cpp b/main/gui/source/init/tasks.cpp index aa7c7772e..0e781606a 100644 --- a/main/gui/source/init/tasks.cpp +++ b/main/gui/source/init/tasks.cpp @@ -410,6 +410,9 @@ namespace hex::init { ContentRegistry::CommunicationInterface::impl::getNetworkEndpoints().clear(); + ContentRegistry::Experiments::impl::getExperiments().clear(); + ContentRegistry::Reports::impl::getGenerators().clear(); + LayoutManager::reset(); ThemeManager::reset(); diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index e3acd6e54..1579d3899 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -42,6 +42,7 @@ add_imhex_plugin( source/content/project.cpp source/content/achievements.cpp source/content/file_extraction.cpp + source/content/report_generators.cpp source/content/dpn/basic_nodes.cpp source/content/dpn/control_nodes.cpp diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 2d1187aac..d0e79adec 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -230,6 +230,8 @@ "hex.builtin.menu.file.export.pattern": "Pattern File", "hex.builtin.menu.file.export.data_processor": "Data Processor Workspace", "hex.builtin.menu.file.export.popup.create": "Cannot export data. Failed to create file!", + "hex.builtin.menu.file.export.report": "Report", + "hex.builtin.menu.file.export.report.popup.export_error": "Failed to create new report file!", "hex.builtin.menu.file.export.title": "Export File", "hex.builtin.menu.file.import": "Import...", "hex.builtin.menu.file.import.base64": "Base64 File", diff --git a/plugins/builtin/source/content/data_formatters.cpp b/plugins/builtin/source/content/data_formatters.cpp index a3a1a01f5..318121e9b 100644 --- a/plugins/builtin/source/content/data_formatters.cpp +++ b/plugins/builtin/source/content/data_formatters.cpp @@ -3,8 +3,10 @@ #include #include + #include #include +#include namespace hex::plugin::builtin { @@ -104,49 +106,7 @@ namespace hex::plugin::builtin { }); ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.hex_view", [](prv::Provider *provider, u64 offset, size_t size) { - constexpr static auto HeaderLine = "Hex View 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"; - std::string result; - result.reserve(std::string(HeaderLine).size() * size / 0x10); - - result += HeaderLine; - - auto reader = prv::ProviderReader(provider); - reader.seek(offset); - reader.setEndAddress((offset + size) - 1); - - u64 address = offset & ~u64(0x0F); - std::string asciiRow; - for (u8 byte : reader) { - if ((address % 0x10) == 0) { - result += hex::format(" {}", asciiRow); - result += hex::format("\n{0:08X} ", address); - - asciiRow.clear(); - - if (address == (offset & ~u64(0x0F))) { - for (u64 i = 0; i < (offset - address); i++) { - result += " "; - asciiRow += " "; - } - address = offset; - } - } - - result += hex::format("{0:02X} ", byte); - asciiRow += std::isprint(byte) ? char(byte) : '.'; - if ((address % 0x10) == 0x07) - result += " "; - - address++; - } - - if ((address % 0x10) != 0x00) - for (u32 i = 0; i < (0x10 - (address % 0x10)); i++) - result += " "; - - result += hex::format(" {}", asciiRow); - - return result; + return hex::generateHexView(offset, size, provider); }); ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.html", [](prv::Provider *provider, u64 offset, size_t size) { @@ -213,4 +173,4 @@ namespace hex::plugin::builtin { }); } -} \ No newline at end of file +} diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index c491bc3de..320edd2f2 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -249,6 +249,35 @@ namespace hex::plugin::builtin { } } + void exportReport() { + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [](auto &) { + std::string data; + + for (const auto &provider : ImHexApi::Provider::getProviders()) { + data += hex::format("# {}", provider->getName()); + data += "\n\n"; + + for (const auto &generator : ContentRegistry::Reports::impl::getGenerators()) { + data += generator.callback(provider); + data += "\n\n"; + } + data += "\n\n"; + } + + TaskManager::doLater([data] { + fs::openFileBrowser(fs::DialogMode::Save, { { "Markdown File", "md" }}, [&data](const auto &path) { + auto file = wolv::io::File(path, wolv::io::File::Mode::Create); + if (!file.isValid()) { + PopupError::open("hex.builtin.menu.file.export.report.popup.export_error"_lang); + return; + } + + file.writeString(data); + }); + }); + }); + } + void exportIPSPatch() { auto provider = ImHexApi::Provider::get(); @@ -420,10 +449,17 @@ namespace hex::plugin::builtin { exportBase64, isProviderDumpable); + /* Language */ ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.as_language" }, 6010, drawExportLanguageMenu, isProviderDumpable); + /* Report */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.report" }, 6020, + Shortcut::None, + exportReport, + ImHexApi::Provider::isValid); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, 6050); /* IPS */ diff --git a/plugins/builtin/source/content/report_generators.cpp b/plugins/builtin/source/content/report_generators.cpp new file mode 100644 index 000000000..320b72bd5 --- /dev/null +++ b/plugins/builtin/source/content/report_generators.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include + +namespace hex::plugin::builtin { + + namespace { + + } + + void registerReportGenerators() { + // Generate provider data description report + ContentRegistry::Reports::addReportProvider([](const prv::Provider *provider) -> std::string { + std::string result; + + result += "## Data description\n\n"; + result += "| Type | Value |\n"; + result += "| ---- | ----- |\n"; + + for (const auto &[type, value] : provider->getDataDescription()) + result += hex::format("| {} | {} |\n", type, value); + + return result; + }); + + // Generate provider overlays report + ContentRegistry::Reports::addReportProvider([](prv::Provider *provider) -> std::string { + std::string result; + + const auto &overlays = provider->getOverlays(); + if (overlays.empty()) + return ""; + + result += "## Overlays\n\n"; + + for (const auto &overlay : overlays) { + result += hex::format("### Overlay 0x{:04X} - 0x{:04X}", overlay->getAddress(), overlay->getAddress() + overlay->getSize() - 1); + result += "\n\n"; + + result += "```\n"; + result += hex::generateHexView(overlay->getAddress(), overlay->getSize(), provider); + result += "\n```\n\n"; + } + + return result; + }); + + } + +} \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_bookmarks.cpp b/plugins/builtin/source/content/views/view_bookmarks.cpp index 3e6bd46e3..09c8e9d63 100644 --- a/plugins/builtin/source/content/views/view_bookmarks.cpp +++ b/plugins/builtin/source/content/views/view_bookmarks.cpp @@ -146,6 +146,30 @@ namespace hex::plugin::builtin { } }); + ContentRegistry::Reports::addReportProvider([this](prv::Provider *provider) -> std::string { + std::string result; + + const auto &bookmars = this->m_bookmarks.get(provider); + if (bookmars.empty()) + return ""; + + result += "## Bookmarks\n\n"; + + for (const auto &bookmark : bookmars) { + result += hex::format("### {} [0x{:04X} - 0x{:04X}]\n\n", hex::changeEndianess(bookmark.color, std::endian::big) >> 8, bookmark.name, bookmark.region.getStartAddress(), bookmark.region.getEndAddress()); + + for (const auto &line : hex::splitString(bookmark.comment, "\n")) + result += hex::format("> {}\n", line); + result += "\n"; + + result += "```\n"; + result += hex::generateHexView(bookmark.region.getStartAddress(), bookmark.region.getSize(), provider); + result += "\n```\n\n"; + } + + return result; + }); + this->registerMenuItems(); } diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 849400569..b2d44588d 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -307,6 +307,7 @@ namespace hex::plugin::builtin { if (this->m_textEditor.IsTextChanged()) { this->m_hasUnevaluatedChanges = true; ImHexApi::Provider::markDirty(); + this->m_sourceCode = this->m_textEditor.GetText(); } if (this->m_hasUnevaluatedChanges && this->m_runningEvaluators == 0 && this->m_runningParsers == 0) { @@ -919,6 +920,7 @@ namespace hex::plugin::builtin { this->evaluatePattern(code, provider); this->m_textEditor.SetText(code); + this->m_sourceCode = code; TaskManager::createBackgroundTask("Parse pattern", [this, code, provider](auto&) { this->parsePattern(code, provider); }); } @@ -1079,6 +1081,7 @@ namespace hex::plugin::builtin { EventManager::subscribe(this, [this](const std::string &code) { this->m_textEditor.SetText(code); + this->m_sourceCode = code; this->m_hasUnevaluatedChanges = true; }); @@ -1113,6 +1116,7 @@ namespace hex::plugin::builtin { EventManager::subscribe(this, [this](prv::Provider *) { if (this->m_syncPatternSourceCode && ImHexApi::Provider::getProviders().empty()) { this->m_textEditor.SetText(""); + this->m_sourceCode = ""; } }); } @@ -1378,6 +1382,23 @@ namespace hex::plugin::builtin { this->m_breakpointHit = false; } }); + + // Generate pattern code report + ContentRegistry::Reports::addReportProvider([this](prv::Provider *provider) -> std::string { + auto patternCode = this->m_sourceCode.get(provider); + + if (wolv::util::trim(patternCode).empty()) + return ""; + + std::string result; + + result += "## Pattern Code\n\n"; + result += "```cpp\n"; + result += patternCode; + result += "\n```\n\n"; + + return result; + }); } } \ No newline at end of file diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index 545b23bfd..644caaccf 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -38,6 +38,7 @@ namespace hex::plugin::builtin { void registerFileHandlers(); void registerProjectHandlers(); void registerAchievements(); + void registerReportGenerators(); void addFooterItems(); void addTitleBarButtons(); @@ -99,6 +100,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { registerProjectHandlers(); registerCommandForwarders(); registerAchievements(); + registerReportGenerators(); addFooterItems(); addTitleBarButtons();