1
0
mirror of synced 2024-11-28 09:30:51 +01:00

feat: Added CSV, TSV and JSON as export options for Find results (#1673)

### Problem description
The default result export functionality of the Find tool is limited to
only exporting data in a nonstandard text format. This PR adds support
for exporting the results in CSV, TSV or JSON format. The PR also
removes the old format.

### Implementation description
I added the classes `ExportFormatter`, `ExportFormatterCsv`,
`ExportFormatterTsv` and `ExportFormatterJson`, with similar
implementations to the pattern data exporters.

~~I also moved the `ViewFind::Occurrence` class into
`hex/helpers/types.hh`, so the exporters can access it.~~

### Screenshots

![image](https://github.com/WerWolv/ImHex/assets/45818400/c7a1016b-6494-416d-a963-86484952837c)

### Additional things
Another small change I made is moving the "{} entries found" line on the
same line as the Search and Reset buttons. I think it looks cleaner this
way, but if anyone disagrees, I can revert it.

---------

Co-authored-by: WerWolv <werwolv98@gmail.com>
This commit is contained in:
SparkyTD 2024-06-18 23:57:55 +03:00 committed by GitHub
parent 6a26c6002b
commit 9b594d81bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 275 additions and 50 deletions

View File

@ -981,12 +981,34 @@ namespace hex {
namespace impl { namespace impl {
using Callback = std::function<std::string(prv::Provider *provider, u64 address, size_t size)>; using Callback = std::function<std::string(prv::Provider *provider, u64 address, size_t size)>;
struct Entry { struct ExportMenuEntry {
UnlocalizedString unlocalizedName; UnlocalizedString unlocalizedName;
Callback callback; Callback callback;
}; };
const std::vector<Entry>& getEntries(); struct FindOccurrence {
Region region;
enum class DecodeType { ASCII, Binary, UTF16, Unsigned, Signed, Float, Double } decodeType;
std::endian endian = std::endian::native;
bool selected;
};
using FindExporterCallback = std::function<std::vector<u8>(const std::vector<FindOccurrence>&, std::function<std::string(FindOccurrence)>)>;
struct FindExporterEntry {
UnlocalizedString unlocalizedName;
std::string fileExtension;
FindExporterCallback callback;
};
/**
* @brief Retrieves a list of all registered data formatters used by the 'File -> Export' menu
*/
const std::vector<ExportMenuEntry>& getExportMenuEntries();
/**
* @brief Retrieves a list of all registered data formatters used in the Results section of the 'Find' view
*/
const std::vector<FindExporterEntry>& getFindExporterEntries();
} }
@ -996,7 +1018,14 @@ namespace hex {
* @param unlocalizedName The unlocalized name of the formatter * @param unlocalizedName The unlocalized name of the formatter
* @param callback The function to call to format the data * @param callback The function to call to format the data
*/ */
void add(const UnlocalizedString &unlocalizedName, const impl::Callback &callback); void addExportMenuEntry(const UnlocalizedString &unlocalizedName, const impl::Callback &callback);
/**
* @brief Adds a new data exporter for Find results
* @param unlocalizedName The unlocalized name of the formatter
* @param callback The function to call to format the data
*/
void addFindExportFormatter(const UnlocalizedString &unlocalizedName, const std::string fileExtension, const impl::FindExporterCallback &callback);
} }

View File

@ -982,17 +982,28 @@ namespace hex {
namespace impl { namespace impl {
static AutoReset<std::vector<Entry>> s_entries; static AutoReset<std::vector<ExportMenuEntry>> s_exportMenuEntries;
const std::vector<Entry>& getEntries() { const std::vector<ExportMenuEntry>& getExportMenuEntries() {
return *s_entries; return *s_exportMenuEntries;
}
static AutoReset<std::vector<FindExporterEntry>> s_findExportEntries;
const std::vector<FindExporterEntry>& getFindExporterEntries() {
return *s_findExportEntries;
} }
} }
void add(const UnlocalizedString &unlocalizedName, const impl::Callback &callback) { void addExportMenuEntry(const UnlocalizedString &unlocalizedName, const impl::Callback &callback) {
log::debug("Registered new data formatter: {}", unlocalizedName.get()); log::debug("Registered new data formatter: {}", unlocalizedName.get());
impl::s_entries->push_back({ unlocalizedName, callback }); impl::s_exportMenuEntries->push_back({ unlocalizedName, callback });
}
void addFindExportFormatter(const UnlocalizedString &unlocalizedName, const std::string fileExtension, const impl::FindExporterCallback &callback) {
log::debug("Registered new export formatter: {}", unlocalizedName.get());
impl::s_findExportEntries->push_back({ unlocalizedName, fileExtension, callback });
} }
} }

View File

@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <vector>
#include <array>
#include <memory>
#include <hex/api/content_registry.hpp>
namespace hex::plugin::builtin::export_fmt {
using Occurrence = hex::ContentRegistry::DataFormatter::impl::FindOccurrence;
/**
* Base class for creating export formatters for different file formats, used in the Results section of the 'Find' view
*/
class ExportFormatter {
public:
explicit ExportFormatter(std::string name) : mName(std::move(name)) {}
virtual ~ExportFormatter() = default;
[[nodiscard]] const std::string &getName() const {
return this->mName;
}
/**
* Main export formatter function
* @param occurrences A list of search occurrences found by the 'Find' view
* @param occurrenceFunc A string formatter function used to transform each occurrence to a string form. May be ignored for custom/binary exporter formats
* @return An array of bytes representing the exported data to be written into the target file
*/
[[nodiscard]] virtual std::vector<u8> format(const std::vector<Occurrence> &occurrences, std::function<std::string(Occurrence)> occurrenceFunc) = 0;
private:
std::string mName;
};
}

View File

@ -0,0 +1,60 @@
#pragma once
#include "export_formatter.hpp"
namespace hex::plugin::builtin::export_fmt {
class ExportFormatterCsv : public ExportFormatter {
public:
ExportFormatterCsv() : ExportFormatter("csv") {}
explicit ExportFormatterCsv(std::string name) : ExportFormatter(std::move(name)) {}
~ExportFormatterCsv() override = default;
[[nodiscard]] std::vector<u8> format(const std::vector<Occurrence> &occurrences, std::function<std::string(Occurrence)> occurrenceFunc) override {
char separator = getSeparatorCharacter();
std::string result;
result += fmt::format("offset{}size{}data\n", separator, separator);
for (const auto &occurrence : occurrences) {
std::string formattedResult = occurrenceFunc(occurrence);
std::string escapedResult;
escapedResult.reserve(formattedResult.size() * 2);
for (char ch : formattedResult) {
if (ch == '"') {
escapedResult += "\"\"";
} else if (ch == '\n' || ch == '\r') {
escapedResult += ' '; // Replace newlines with spaces
} else {
escapedResult += ch;
}
}
bool needsQuotes = escapedResult.find(separator) != std::string::npos || escapedResult.find('"') != std::string::npos;
if (needsQuotes) {
escapedResult.insert(0, 1, '"');
escapedResult.push_back('"');
}
const auto line = fmt::format("0x{:08X}{}0x{}{}{}",
occurrence.region.getStartAddress(),
separator,
occurrence.region.getSize(),
separator,
escapedResult);
result += line;
result += '\n';
}
return { result.begin(), result.end() };
}
protected:
[[nodiscard]] virtual char getSeparatorCharacter() const { return ','; }
};
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "export_formatter.hpp"
#include <nlohmann/json.hpp>
namespace hex::plugin::builtin::export_fmt {
class ExportFormatterJson : public ExportFormatter {
public:
ExportFormatterJson() : ExportFormatter("json") {}
~ExportFormatterJson() override = default;
std::vector<u8> format(const std::vector<Occurrence> &occurrences, std::function<std::string (Occurrence)> occurrenceFunc) override {
nlohmann::json resultJson;
for (const auto &occurrence : occurrences) {
std::string formattedResult = occurrenceFunc(occurrence);
nlohmann::json obj = {
{ "offset", occurrence.region.getStartAddress() },
{ "size", occurrence.region.getSize() },
{ "data", formattedResult }
};
resultJson.push_back(obj);
}
auto result = resultJson.dump(4);
return { result.begin(), result.end() };
}
};
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "export_formatter_csv.hpp"
namespace hex::plugin::builtin::export_fmt {
class ExportFormatterTsv : public ExportFormatterCsv {
public:
ExportFormatterTsv() : ExportFormatterCsv("tsv") {}
~ExportFormatterTsv() override = default;
protected:
[[nodiscard]] char getSeparatorCharacter() const override { return '\t'; }
};
}

View File

@ -11,6 +11,8 @@
#include <wolv/container/interval_tree.hpp> #include <wolv/container/interval_tree.hpp>
#include <hex/api/content_registry.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
class ViewFind : public View::Window { class ViewFind : public View::Window {
@ -22,12 +24,7 @@ namespace hex::plugin::builtin {
private: private:
struct Occurrence { using Occurrence = hex::ContentRegistry::DataFormatter::impl::FindOccurrence;
Region region;
enum class DecodeType { ASCII, Binary, UTF16, Unsigned, Signed, Float, Double } decodeType;
std::endian endian = std::endian::native;
bool selected;
};
struct BinaryPattern { struct BinaryPattern {
u8 mask, value; u8 mask, value;

View File

@ -8,6 +8,10 @@
#include <hex/helpers/crypto.hpp> #include <hex/helpers/crypto.hpp>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
#include <content/export_formatters/export_formatter_csv.hpp>
#include <content/export_formatters/export_formatter_tsv.hpp>
#include <content/export_formatters/export_formatter_json.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
static std::string formatLanguageArray(prv::Provider *provider, u64 offset, size_t size, const std::string &start, const std::string &byteFormat, const std::string &end, bool removeFinalDelimiter= false) { static std::string formatLanguageArray(prv::Provider *provider, u64 offset, size_t size, const std::string &start, const std::string &byteFormat, const std::string &end, bool removeFinalDelimiter= false) {
@ -46,57 +50,57 @@ namespace hex::plugin::builtin {
void registerDataFormatters() { void registerDataFormatters() {
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.c", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.c", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, hex::format("const uint8_t data[{0}] = {{", size), "0x{0:02X}, ", "};"); return formatLanguageArray(provider, offset, size, hex::format("const uint8_t data[{0}] = {{", size), "0x{0:02X}, ", "};");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.cpp", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.cpp", [](prv::Provider *provider, u64 offset, size_t size) {
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.copy_as.name"); AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.copy_as.name");
return formatLanguageArray(provider, offset, size, hex::format("constexpr std::array<uint8_t, {0}> data = {{", size), "0x{0:02X}, ", "};"); return formatLanguageArray(provider, offset, size, hex::format("constexpr std::array<uint8_t, {0}> data = {{", size), "0x{0:02X}, ", "};");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.java", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.java", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "final byte[] data = {", "0x{0:02X}, ", "};"); return formatLanguageArray(provider, offset, size, "final byte[] data = {", "0x{0:02X}, ", "};");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.csharp", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.csharp", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "const byte[] data = {", "0x{0:02X}, ", "};"); return formatLanguageArray(provider, offset, size, "const byte[] data = {", "0x{0:02X}, ", "};");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.rust", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.rust", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, hex::format("let data: [u8; 0x{0:02X}] = [", size), "0x{0:02X}, ", "];"); return formatLanguageArray(provider, offset, size, hex::format("let data: [u8; 0x{0:02X}] = [", size), "0x{0:02X}, ", "];");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.python", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.python", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "data = bytes([", "0x{0:02X}, ", "])"); return formatLanguageArray(provider, offset, size, "data = bytes([", "0x{0:02X}, ", "])");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.js", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.js", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "const data = new Uint8Array([", "0x{0:02X}, ", "]);"); return formatLanguageArray(provider, offset, size, "const data = new Uint8Array([", "0x{0:02X}, ", "]);");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.lua", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.lua", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "data = {", "0x{0:02X}, ", "}"); return formatLanguageArray(provider, offset, size, "data = {", "0x{0:02X}, ", "}");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.go", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.go", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "data := [...]byte{", "0x{0:02X}, ", "}", false); return formatLanguageArray(provider, offset, size, "data := [...]byte{", "0x{0:02X}, ", "}", false);
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.crystal", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.crystal", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "data = [", "0x{0:02X}, ", "] of UInt8"); return formatLanguageArray(provider, offset, size, "data = [", "0x{0:02X}, ", "] of UInt8");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.swift", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.swift", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, "let data: [Uint8] = [", "0x{0:02X}, ", "]"); return formatLanguageArray(provider, offset, size, "let data: [Uint8] = [", "0x{0:02X}, ", "]");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.pascal", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.pascal", [](prv::Provider *provider, u64 offset, size_t size) {
return formatLanguageArray(provider, offset, size, hex::format("data: array[0..{0}] of Byte = (", size - 1), "${0:02X}, ", ")"); return formatLanguageArray(provider, offset, size, hex::format("data: array[0..{0}] of Byte = (", size - 1), "${0:02X}, ", ")");
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.base64", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.base64", [](prv::Provider *provider, u64 offset, size_t size) {
std::vector<u8> data(size, 0x00); std::vector<u8> data(size, 0x00);
provider->read(offset, data.data(), size); provider->read(offset, data.data(), size);
@ -105,11 +109,11 @@ namespace hex::plugin::builtin {
return std::string(result.begin(), result.end()); return std::string(result.begin(), result.end());
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.hex_view", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.hex_view", [](prv::Provider *provider, u64 offset, size_t size) {
return hex::generateHexView(offset, size, provider); return hex::generateHexView(offset, size, provider);
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.html", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::addExportMenuEntry("hex.builtin.view.hex_editor.copy.html", [](prv::Provider *provider, u64 offset, size_t size) {
std::string result = std::string result =
"<div>\n" "<div>\n"
" <style type=\"text/css\">\n" " <style type=\"text/css\">\n"
@ -171,6 +175,21 @@ namespace hex::plugin::builtin {
return result; return result;
}); });
ContentRegistry::DataFormatter::addFindExportFormatter("csv", "csv", [](const std::vector<ContentRegistry::DataFormatter::impl::FindOccurrence>& occurrences, const auto &transformFunc) {
export_fmt::ExportFormatterCsv formatter;
return formatter.format(occurrences, transformFunc);
});
ContentRegistry::DataFormatter::addFindExportFormatter("tsv", "tsv", [](const std::vector<ContentRegistry::DataFormatter::impl::FindOccurrence>& occurrences, const auto &transformFunc) {
export_fmt::ExportFormatterTsv formatter;
return formatter.format(occurrences, transformFunc);
});
ContentRegistry::DataFormatter::addFindExportFormatter("json", "json", [](const std::vector<ContentRegistry::DataFormatter::impl::FindOccurrence>& occurrences, const auto &transformFunc) {
export_fmt::ExportFormatterJson formatter;
return formatter.format(occurrences, transformFunc);
});
} }
} }

View File

@ -210,7 +210,7 @@ namespace hex::plugin::builtin {
} }
void drawExportLanguageMenu() { void drawExportLanguageMenu() {
for (const auto &formatter : ContentRegistry::DataFormatter::impl::getEntries()) { for (const auto &formatter : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) {
if (ImGui::MenuItem(Lang(formatter.unlocalizedName), nullptr, false, ImHexApi::Provider::get()->getActualSize() > 0)) { if (ImGui::MenuItem(Lang(formatter.unlocalizedName), nullptr, false, ImHexApi::Provider::get()->getActualSize() > 0)) {
fs::openFileBrowser(fs::DialogMode::Save, {}, [&formatter](const auto &path) { fs::openFileBrowser(fs::DialogMode::Save, {}, [&formatter](const auto &path) {
TaskManager::createTask("Exporting data", TaskManager::NoProgress, [&formatter, path](auto&){ TaskManager::createTask("Exporting data", TaskManager::NoProgress, [&formatter, path](auto&){

View File

@ -168,7 +168,7 @@ namespace hex::plugin::builtin {
return hex::format("{}", value); return hex::format("{}", value);
} }
std::vector<ViewFind::Occurrence> ViewFind::searchStrings(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Strings &settings) { std::vector<hex::ContentRegistry::DataFormatter::impl::FindOccurrence> ViewFind::searchStrings(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Strings &settings) {
using enum SearchSettings::StringType; using enum SearchSettings::StringType;
std::vector<Occurrence> results; std::vector<Occurrence> results;
@ -254,7 +254,7 @@ namespace hex::plugin::builtin {
return results; return results;
} }
std::vector<ViewFind::Occurrence> ViewFind::searchSequence(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Sequence &settings) { std::vector<hex::ContentRegistry::DataFormatter::impl::FindOccurrence> ViewFind::searchSequence(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Sequence &settings) {
std::vector<Occurrence> results; std::vector<Occurrence> results;
auto reader = prv::ProviderReader(provider); auto reader = prv::ProviderReader(provider);
@ -335,7 +335,7 @@ namespace hex::plugin::builtin {
return results; return results;
} }
std::vector<ViewFind::Occurrence> ViewFind::searchRegex(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Regex &settings) { std::vector<hex::ContentRegistry::DataFormatter::impl::FindOccurrence> ViewFind::searchRegex(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Regex &settings) {
auto stringOccurrences = searchStrings(task, provider, searchRegion, SearchSettings::Strings { auto stringOccurrences = searchStrings(task, provider, searchRegion, SearchSettings::Strings {
.minLength = settings.minLength, .minLength = settings.minLength,
.nullTermination = settings.nullTermination, .nullTermination = settings.nullTermination,
@ -369,7 +369,7 @@ namespace hex::plugin::builtin {
return result; return result;
} }
std::vector<ViewFind::Occurrence> ViewFind::searchBinaryPattern(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::BinaryPattern &settings) { std::vector<hex::ContentRegistry::DataFormatter::impl::FindOccurrence> ViewFind::searchBinaryPattern(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::BinaryPattern &settings) {
std::vector<Occurrence> results; std::vector<Occurrence> results;
auto reader = prv::ProviderReader(provider); auto reader = prv::ProviderReader(provider);
@ -422,7 +422,7 @@ namespace hex::plugin::builtin {
return results; return results;
} }
std::vector<ViewFind::Occurrence> ViewFind::searchValue(Task &task, prv::Provider *provider, Region searchRegion, const SearchSettings::Value &settings) { std::vector<hex::ContentRegistry::DataFormatter::impl::FindOccurrence> ViewFind::searchValue(Task &task, prv::Provider *provider, Region searchRegion, const SearchSettings::Value &settings) {
std::vector<Occurrence> results; std::vector<Occurrence> results;
auto reader = prv::ProviderReader(provider); auto reader = prv::ProviderReader(provider);
@ -888,7 +888,6 @@ namespace hex::plugin::builtin {
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
ImGuiExt::TextFormatted("hex.builtin.view.find.search.entries"_lang, m_foundOccurrences->size());
ImGui::BeginDisabled(m_foundOccurrences->empty()); ImGui::BeginDisabled(m_foundOccurrences->empty());
{ {
@ -902,6 +901,10 @@ namespace hex::plugin::builtin {
} }
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::SameLine();
ImGuiExt::TextFormatted("hex.builtin.view.find.search.entries"_lang, m_foundOccurrences->size());
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
@ -911,7 +914,7 @@ namespace hex::plugin::builtin {
auto &currOccurrences = *m_sortedOccurrences; auto &currOccurrences = *m_sortedOccurrences;
ImGui::PushItemWidth(-15_scaled); ImGui::PushItemWidth(-30_scaled);
auto prevFilterLength = m_currFilter->length(); auto prevFilterLength = m_currFilter->length();
if (ImGuiExt::InputTextIcon("##filter", ICON_VS_FILTER, *m_currFilter)) { if (ImGuiExt::InputTextIcon("##filter", ICON_VS_FILTER, *m_currFilter)) {
if (prevFilterLength > m_currFilter->length()) if (prevFilterLength > m_currFilter->length())
@ -936,22 +939,38 @@ namespace hex::plugin::builtin {
ImGui::SameLine(); ImGui::SameLine();
const auto startPos = ImGui::GetCursorPos();
ImGui::BeginDisabled(m_sortedOccurrences->empty()); ImGui::BeginDisabled(m_sortedOccurrences->empty());
if (ImGuiExt::DimmedIconButton(ICON_VS_EXPORT, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { if (ImGuiExt::DimmedIconButton(ICON_VS_EXPORT, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
fs::openFileBrowser(fs::DialogMode::Save, {}, [this](const std::fs::path &path) { ImGui::OpenPopup("ExportResults");
if (path.empty()) }
return; ImGui::EndDisabled();
ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImVec2(startPos.x, ImGui::GetCursorPosY()));
if (ImGui::BeginPopup("ExportResults")) {
for (const auto &formatter : ContentRegistry::DataFormatter::impl::getFindExporterEntries()) {
const auto formatterName = formatter.unlocalizedName;
const auto name = toUpper(formatterName);
const auto &extension = formatter.fileExtension;
if (ImGui::MenuItem(name.c_str())) {
fs::openFileBrowser(fs::DialogMode::Save, { { name.c_str(), extension.c_str() } }, [&](const std::fs::path &path) {
wolv::io::File file(path, wolv::io::File::Mode::Create); wolv::io::File file(path, wolv::io::File::Mode::Create);
if (!file.isValid()) if (!file.isValid())
return; return;
for (const auto &occurrence : *m_sortedOccurrences) { auto result = formatter.callback(
file.writeString(hex::format("0x{:08X}\t{}\n", occurrence.region.getStartAddress(), this->decodeValue(ImHexApi::Provider::get(), occurrence))); m_sortedOccurrences.get(provider),
} [&](Occurrence o){ return this->decodeValue(provider, o); });
file.writeVector(result);
file.close();
}); });
} }
ImGui::EndDisabled(); }
ImGui::EndPopup();
}
if (ImGui::BeginTable("##entries", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImMax(ImGui::GetContentRegionAvail(), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)))) { if (ImGui::BeginTable("##entries", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImMax(ImGui::GetContentRegionAvail(), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)))) {
ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupScrollFreeze(0, 1);

View File

@ -1164,7 +1164,7 @@ namespace hex::plugin::builtin {
auto provider = ImHexApi::Provider::get(); auto provider = ImHexApi::Provider::get();
bool enabled = ImHexApi::HexEditor::isSelectionValid(); bool enabled = ImHexApi::HexEditor::isSelectionValid();
for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::impl::getEntries()) { for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) {
if (ImGui::MenuItem(Lang(unlocalizedName), nullptr, false, enabled)) { if (ImGui::MenuItem(Lang(unlocalizedName), nullptr, false, enabled)) {
ImGui::SetClipboardText( ImGui::SetClipboardText(
callback( callback(