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:
parent
6a26c6002b
commit
9b594d81bd
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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 ','; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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() };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -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'; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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&){
|
||||||
|
@ -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,23 +939,39 @@ 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;
|
|
||||||
|
|
||||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
|
||||||
if (!file.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (const auto &occurrence : *m_sortedOccurrences) {
|
|
||||||
file.writeString(hex::format("0x{:08X}\t{}\n", occurrence.region.getStartAddress(), this->decodeValue(ImHexApi::Provider::get(), occurrence)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
ImGui::EndDisabled();
|
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);
|
||||||
|
if (!file.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto result = formatter.callback(
|
||||||
|
m_sortedOccurrences.get(provider),
|
||||||
|
[&](Occurrence o){ return this->decodeValue(provider, o); });
|
||||||
|
|
||||||
|
file.writeVector(result);
|
||||||
|
file.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
ImGui::TableSetupColumn("hex.ui.common.offset"_lang, 0, -1, ImGui::GetID("offset"));
|
ImGui::TableSetupColumn("hex.ui.common.offset"_lang, 0, -1, ImGui::GetID("offset"));
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user