feat: Add Markdown Report export option (#1441)

This commit is contained in:
Nik 2023-11-22 08:26:31 +01:00 committed by GitHub
parent 909f4b7fe8
commit 095da62250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 259 additions and 46 deletions

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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 ) });
}
}
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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

View file

@ -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",

View 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 {
});
}
}
}

View file

@ -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 */

View 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;
});
}
}

View file

@ -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();
}

View file

@ -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;
});
}
}

View file

@ -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();