feat: Add Markdown Report export option (#1441)
This commit is contained in:
parent
909f4b7fe8
commit
095da62250
@ -1000,6 +1000,24 @@ namespace hex {
|
||||
|
||||
[[nodiscard]] bool isExperimentEnabled(const std::string &experimentName);
|
||||
}
|
||||
|
||||
namespace Reports {
|
||||
|
||||
namespace impl {
|
||||
|
||||
using Callback = std::function<std::string(prv::Provider*)>;
|
||||
|
||||
struct ReportGenerator {
|
||||
Callback callback;
|
||||
};
|
||||
|
||||
std::vector<ReportGenerator>& getGenerators();
|
||||
|
||||
}
|
||||
|
||||
void addReportProvider(impl::Callback callback);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ struct ImVec2;
|
||||
|
||||
namespace hex {
|
||||
|
||||
namespace prv {
|
||||
class Provider;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::vector<T> sampleData(const std::vector<T> &data, size_t count) {
|
||||
size_t stride = std::max(1.0, double(data.size()) / count);
|
||||
@ -286,4 +290,7 @@ namespace hex {
|
||||
|
||||
[[nodiscard]] std::optional<std::fs::path> getInitialFilePath();
|
||||
|
||||
[[nodiscard]] std::string generateHexView(u64 offset, u64 size, prv::Provider *provider);
|
||||
[[nodiscard]] std::string generateHexView(u64 offset, const std::vector<u8> &data);
|
||||
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ namespace hex::prv {
|
||||
|
||||
[[nodiscard]] Overlay *newOverlay();
|
||||
void deleteOverlay(Overlay *overlay);
|
||||
[[nodiscard]] const std::list<std::unique_ptr<Overlay>> &getOverlays();
|
||||
[[nodiscard]] const std::list<std::unique_ptr<Overlay>> &getOverlays() const;
|
||||
|
||||
[[nodiscard]] size_t getPageSize() const;
|
||||
void setPageSize(size_t pageSize);
|
||||
|
@ -1098,4 +1098,22 @@ namespace hex {
|
||||
|
||||
}
|
||||
|
||||
namespace ContentRegistry::Reports {
|
||||
|
||||
namespace impl {
|
||||
|
||||
std::vector<ReportGenerator> &getGenerators() {
|
||||
static std::vector<ReportGenerator> generators;
|
||||
|
||||
return generators;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void addReportProvider(impl::Callback callback) {
|
||||
impl::getGenerators().push_back(impl::ReportGenerator { std::move(callback ) });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <hex/helpers/utils_linux.hpp>
|
||||
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
@ -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<u8> &data) {
|
||||
return generateHexViewImpl(offset, data.begin(), data.end());
|
||||
}
|
||||
|
||||
}
|
@ -173,7 +173,7 @@ namespace hex::prv {
|
||||
});
|
||||
}
|
||||
|
||||
const std::list<std::unique_ptr<Overlay>> &Provider::getOverlays() {
|
||||
const std::list<std::unique_ptr<Overlay>> &Provider::getOverlays() const {
|
||||
return this->m_overlays;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
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 {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
53
plugins/builtin/source/content/report_generators.cpp
Normal file
53
plugins/builtin/source/content/report_generators.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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("### <span style=\"background-color: #{:06X}80\">{} [0x{:04X} - 0x{:04X}]</span>\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();
|
||||
}
|
||||
|
||||
|
@ -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<RequestSetPatternLanguageCode>(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<EventProviderClosed>(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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user