mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-28 07:47:03 -05:00
<!-- Please provide as much information as possible about what your PR aims to do. PRs with no description will most likely be closed until more information is provided. If you're planing on changing fundamental behaviour or add big new features, please open a GitHub Issue first before starting to work on it. If it's not something big and you still want to contact us about it, feel free to do so ! --> ### Problem description <!-- Describe the bug that you fixed/feature request that you implemented, or link to an existing issue describing it --> ### Implementation description <!-- Explain what you did to correct the problem --> ### Screenshots <!-- If your change is visual, take a screenshot showing it. Ideally, make before/after sceenshots --> ### Additional things <!-- Anything else you would like to say -->
1315 lines
59 KiB
C++
1315 lines
59 KiB
C++
#include "content/views/view_find.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <hex/api/achievement_manager.hpp>
|
|
#include <hex/api/imhex_api/hex_editor.hpp>
|
|
#include <hex/api/events/events_interaction.hpp>
|
|
#include <hex/api/content_registry/user_interface.hpp>
|
|
#include <hex/trace/stacktrace.hpp>
|
|
|
|
#include <hex/providers/buffered_reader.hpp>
|
|
|
|
#include <fonts/vscode_icons.hpp>
|
|
#include <imgui_internal.h>
|
|
|
|
#include <array>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <barrier>
|
|
|
|
#include <boost/regex.hpp>
|
|
|
|
#include <content/helpers/constants.hpp>
|
|
#include <hex/helpers/crypto.hpp>
|
|
#include <hex/helpers/default_paths.hpp>
|
|
#include <toasts/toast_notification.hpp>
|
|
|
|
namespace hex::plugin::builtin {
|
|
|
|
ViewFind::ViewFind() : View::Window("hex.builtin.view.find.name", ICON_VS_SEARCH) {
|
|
const static auto HighlightColor = [] { return (ImGuiExt::GetCustomColorU32(ImGuiCustomCol_FindHighlight) & 0x00FFFFFF) | 0x70000000; };
|
|
|
|
ImHexApi::HexEditor::addBackgroundHighlightingProvider([this](u64 address, const u8* data, size_t size, bool) -> std::optional<color_t> {
|
|
std::ignore = data;
|
|
std::ignore = size;
|
|
|
|
if (m_searchTask.isRunning())
|
|
return { };
|
|
|
|
if (!m_occurrenceTree->overlapping({ .start=address, .end=address }).empty())
|
|
return HighlightColor();
|
|
else
|
|
return std::nullopt;
|
|
});
|
|
|
|
ImHexApi::HexEditor::addTooltipProvider([this](u64 address, const u8* data, size_t size) {
|
|
std::ignore = data;
|
|
std::ignore = size;
|
|
|
|
if (m_searchTask.isRunning())
|
|
return;
|
|
|
|
auto occurrences = m_occurrenceTree->overlapping({ .start=address, .end=address + size });
|
|
if (occurrences.empty())
|
|
return;
|
|
|
|
ImGui::BeginTooltip();
|
|
|
|
for (const auto &occurrence : occurrences) {
|
|
ImGui::PushID(&occurrence);
|
|
if (ImGui::BeginTable("##tooltips", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
|
|
{
|
|
auto region = occurrence.value->region;
|
|
const auto value = this->decodeValue(ImHexApi::Provider::get(), *occurrence.value, 256);
|
|
|
|
ImGui::ColorButton("##color", ImColor(HighlightColor()), ImGuiColorEditFlags_AlphaOpaque);
|
|
ImGui::SameLine(0, 10);
|
|
ImGuiExt::TextFormatted("{} ", value);
|
|
|
|
if (ImGui::GetIO().KeyShift) {
|
|
ImGui::Indent();
|
|
if (ImGui::BeginTable("##extra_info", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) {
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGuiExt::TextFormatted("{}: ", "hex.ui.common.region"_lang);
|
|
ImGui::TableNextColumn();
|
|
ImGuiExt::TextFormatted("[ 0x{:08X} - 0x{:08X} ]", region.getStartAddress(), region.getEndAddress());
|
|
|
|
auto demangledValue = trace::demangle(value);
|
|
|
|
if (value != demangledValue) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGuiExt::TextFormatted("{}: ", "hex.builtin.view.find.demangled"_lang);
|
|
ImGui::TableNextColumn();
|
|
ImGuiExt::TextFormatted("{}", demangledValue);
|
|
}
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
ImGui::Unindent();
|
|
}
|
|
}
|
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_TableRowBg, HighlightColor());
|
|
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, HighlightColor());
|
|
ImGui::EndTable();
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
ImGui::EndTooltip();
|
|
});
|
|
|
|
ShortcutManager::addShortcut(this, CTRLCMD + Keys::A, "hex.builtin.view.find.shortcut.select_all", [this] {
|
|
if (m_filterTask.isRunning())
|
|
return;
|
|
if (m_searchTask.isRunning())
|
|
return;
|
|
|
|
for (auto &occurrence : *m_sortedOccurrences)
|
|
occurrence.selected = true;
|
|
});
|
|
|
|
/* Find Selection */
|
|
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.find.find_selection" }, ICON_VS_SEARCH_SPARKLE, 1950, CTRLCMD + SHIFT + Keys::F, [&] {
|
|
auto selection = ImHexApi::HexEditor::getSelection();
|
|
if (!selection.has_value())
|
|
return;
|
|
|
|
std::string sequence;
|
|
{
|
|
std::vector<u8> buffer(selection->getSize());
|
|
selection->getProvider()->read(selection->getStartAddress(), buffer.data(), buffer.size());
|
|
sequence = hex::crypt::encode16(buffer);
|
|
}
|
|
|
|
m_searchSettings.mode = SearchSettings::Mode::BinaryPattern;
|
|
m_searchSettings.region = { .address=selection->getProvider()->getBaseAddress(), .size=selection->getProvider()->getActualSize() };
|
|
m_searchSettings.binaryPattern = {
|
|
.input = sequence,
|
|
.pattern = hex::BinaryPattern(sequence),
|
|
.alignment = 1
|
|
};
|
|
|
|
this->runSearch();
|
|
this->bringToFront();
|
|
}, []{ return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid(); },
|
|
ContentRegistry::Views::getViewByName("hex.builtin.view.hex_editor.name"));
|
|
}
|
|
|
|
template<typename Type, typename StorageType>
|
|
static std::tuple<bool, std::variant<u64, i64, float, double>, size_t> parseNumericValue(const std::string &string) {
|
|
static_assert(sizeof(StorageType) >= sizeof(Type));
|
|
|
|
StorageType value;
|
|
|
|
std::size_t processed = 0;
|
|
try {
|
|
if constexpr (std::floating_point<Type>)
|
|
value = std::stod(string, &processed);
|
|
else if constexpr (std::signed_integral<Type>)
|
|
value = std::stoll(string, &processed, 0);
|
|
else
|
|
value = std::stoull(string, &processed, 0);
|
|
} catch (std::exception &) {
|
|
return { false, { }, 0 };
|
|
}
|
|
|
|
if (processed != string.size())
|
|
return { false, { }, 0 };
|
|
|
|
if (value < std::numeric_limits<Type>::lowest() || value > std::numeric_limits<Type>::max())
|
|
return { false, { }, 0 };
|
|
|
|
return { true, value, sizeof(Type) };
|
|
}
|
|
|
|
std::tuple<bool, std::variant<u64, i64, float, double>, size_t> ViewFind::parseNumericValueInput(const std::string &input, SearchSettings::Value::Type type) {
|
|
switch (type) {
|
|
using enum SearchSettings::Value::Type;
|
|
|
|
case U8: return parseNumericValue<u8, u64>(input);
|
|
case U16: return parseNumericValue<u16, u64>(input);
|
|
case U32: return parseNumericValue<u32, u64>(input);
|
|
case U64: return parseNumericValue<u64, u64>(input);
|
|
case I8: return parseNumericValue<i8, i64>(input);
|
|
case I16: return parseNumericValue<i16, i64>(input);
|
|
case I32: return parseNumericValue<i32, i64>(input);
|
|
case I64: return parseNumericValue<i64, i64>(input);
|
|
case F32: return parseNumericValue<float, float>(input);
|
|
case F64: return parseNumericValue<double, double>(input);
|
|
default: return { false, { }, 0 };
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
static std::string formatBytes(const std::vector<u8> &bytes, std::endian endian) {
|
|
if (bytes.size() > sizeof(T))
|
|
return { };
|
|
|
|
T value = 0x00;
|
|
std::memcpy(&value, bytes.data(), bytes.size());
|
|
|
|
value = hex::changeEndianness(value, bytes.size(), endian);
|
|
|
|
if (std::signed_integral<T>)
|
|
value = T(hex::signExtend(bytes.size() * 8, value));
|
|
|
|
return fmt::format("{}", value);
|
|
}
|
|
|
|
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;
|
|
|
|
std::vector<Occurrence> results;
|
|
|
|
if (settings.type == ASCII_UTF16BE || settings.type == ASCII_UTF16LE) {
|
|
auto newSettings = settings;
|
|
|
|
newSettings.type = ASCII;
|
|
auto asciiResults = searchStrings(task, provider, searchRegion, newSettings);
|
|
std::ranges::copy(asciiResults, std::back_inserter(results));
|
|
|
|
if (settings.type == ASCII_UTF16BE) {
|
|
newSettings.type = UTF16BE;
|
|
auto utf16Results = searchStrings(task, provider, searchRegion, newSettings);
|
|
std::ranges::copy(utf16Results, std::back_inserter(results));
|
|
} else if (settings.type == ASCII_UTF16LE) {
|
|
newSettings.type = UTF16LE;
|
|
auto utf16Results = searchStrings(task, provider, searchRegion, newSettings);
|
|
std::ranges::copy(utf16Results, std::back_inserter(results));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
auto reader = prv::ProviderReader(provider);
|
|
reader.seek(searchRegion.getStartAddress());
|
|
reader.setEndAddress(searchRegion.getEndAddress());
|
|
|
|
const auto [decodeType, endian] = [&]() -> std::pair<Occurrence::DecodeType, std::endian> {
|
|
if (settings.type == ASCII)
|
|
return { Occurrence::DecodeType::ASCII, std::endian::native };
|
|
if (settings.type == UTF8)
|
|
return { Occurrence::DecodeType::UTF8, std::endian::native };
|
|
else if (settings.type == SearchSettings::StringType::UTF16BE)
|
|
return { Occurrence::DecodeType::UTF16, std::endian::big };
|
|
else if (settings.type == SearchSettings::StringType::UTF16LE)
|
|
return { Occurrence::DecodeType::UTF16, std::endian::little };
|
|
else
|
|
return { Occurrence::DecodeType::Binary, std::endian::native };
|
|
}();
|
|
|
|
const auto validAscii = [&](u8 byte) {
|
|
return
|
|
(settings.lowerCaseLetters && std::islower(byte)) ||
|
|
(settings.upperCaseLetters && std::isupper(byte)) ||
|
|
(settings.numbers && std::isdigit(byte)) ||
|
|
(settings.spaces && std::isspace(byte) && byte != '\r' && byte != '\n') ||
|
|
(settings.underscores && byte == '_') ||
|
|
(settings.symbols && std::ispunct(byte) && !std::isspace(byte)) ||
|
|
(settings.lineFeeds && (byte == '\r' || byte == '\n'));
|
|
};
|
|
|
|
i64 countedCharacters = 0;
|
|
u64 startAddress = reader.begin().getAddress();
|
|
u64 endAddress = reader.end().getAddress();
|
|
|
|
u64 progress = 0;
|
|
i8 remainingCharacters = 0;
|
|
for (const u8 byte : reader) {
|
|
bool validChar = false;
|
|
|
|
if (settings.type == ASCII) {
|
|
validChar = validAscii(byte);
|
|
} else if (settings.type == UTF16LE) {
|
|
// Check if second byte of UTF-16 encoded string is 0x00
|
|
if (countedCharacters % 2 == 1)
|
|
validChar = byte == 0x00;
|
|
else
|
|
validChar = validAscii(byte);
|
|
} else if (settings.type == UTF16BE) {
|
|
// Check if first byte of UTF-16 encoded string is 0x00
|
|
if (countedCharacters % 2 == 0)
|
|
validChar = byte == 0x00;
|
|
else
|
|
validChar = validAscii(byte);
|
|
} else if (settings.type == UTF8) {
|
|
if (remainingCharacters > 0) {
|
|
// Expect continuation byte
|
|
if ((byte & 0b1100'0000) == 0b1000'0000) {
|
|
validChar = true;
|
|
remainingCharacters -= 1;
|
|
} else {
|
|
validChar = false; // broken sequence
|
|
remainingCharacters = 0;
|
|
}
|
|
} else {
|
|
// New character
|
|
if (byte <= 0x7F) {
|
|
// ASCII
|
|
validChar = validAscii(byte);
|
|
remainingCharacters = 0;
|
|
} else if ((byte & 0b1110'0000) == 0b1100'0000) {
|
|
// 2-byte start (U+80..U+7FF)
|
|
validChar = (byte >= 0xC2); // exclude overlongs (0xC0, 0xC1)
|
|
remainingCharacters = 1;
|
|
} else if ((byte & 0b1111'0000) == 0b1110'0000) {
|
|
// 3-byte start (U+800..U+FFFF)
|
|
validChar = byte != 0xE0 && byte != 0xED;
|
|
// E0 must be followed by >= 0xA0, ED must be <= 0x9F (avoid surrogates)
|
|
remainingCharacters = 2;
|
|
} else if ((byte & 0b1111'1000) == 0b1111'0000) {
|
|
// 4-byte start (U+10000..U+10FFFF)
|
|
validChar = (byte <= 0xF4); // reject > U+10FFFF
|
|
remainingCharacters = 3;
|
|
} else {
|
|
validChar = false;
|
|
remainingCharacters = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
task.update(progress);
|
|
|
|
if (validChar)
|
|
countedCharacters++;
|
|
if (!validChar || startAddress + countedCharacters == endAddress) {
|
|
if (countedCharacters >= settings.minLength) {
|
|
if (!settings.nullTermination || byte == 0x00) {
|
|
results.push_back(Occurrence { Region { .address=startAddress, .size=size_t(countedCharacters) }, endian, decodeType, false, {} });
|
|
}
|
|
}
|
|
|
|
startAddress += countedCharacters + 1;
|
|
countedCharacters = 0;
|
|
progress = startAddress - searchRegion.getStartAddress();
|
|
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
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;
|
|
|
|
auto reader = prv::ProviderReader(provider);
|
|
reader.seek(searchRegion.getStartAddress());
|
|
reader.setEndAddress(searchRegion.getEndAddress());
|
|
|
|
auto input = hex::decodeByteString(settings.sequence);
|
|
if (input.empty())
|
|
return { };
|
|
|
|
std::vector<u8> bytes;
|
|
auto decodeType = Occurrence::DecodeType::Binary;
|
|
std::endian endian;
|
|
switch (settings.type) {
|
|
default:
|
|
case SearchSettings::StringType::ASCII:
|
|
bytes = input;
|
|
decodeType = Occurrence::DecodeType::ASCII;
|
|
endian = std::endian::native;
|
|
break;
|
|
case SearchSettings::StringType::UTF16LE: {
|
|
auto wString = hex::utf8ToUtf16({ input.begin(), input.end() });
|
|
|
|
bytes.resize(wString.size() * 2);
|
|
std::memcpy(bytes.data(), wString.data(), bytes.size());
|
|
decodeType = Occurrence::DecodeType::UTF16;
|
|
endian = std::endian::little;
|
|
|
|
break;
|
|
}
|
|
case SearchSettings::StringType::UTF16BE: {
|
|
auto wString = hex::utf8ToUtf16({ input.begin(), input.end() });
|
|
|
|
bytes.resize(wString.size() * 2);
|
|
std::memcpy(bytes.data(), wString.data(), bytes.size());
|
|
decodeType = Occurrence::DecodeType::UTF16;
|
|
endian = std::endian::big;
|
|
|
|
for (size_t i = 0; i < bytes.size(); i += 2)
|
|
std::swap(bytes[i], bytes[i + 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto occurrence = reader.begin();
|
|
u64 progress = 0;
|
|
|
|
auto searchPredicate = [&]() -> bool(*)(u8, u8) {
|
|
if (!settings.ignoreCase)
|
|
return [](u8 left, u8 right) -> bool {
|
|
return left == right;
|
|
};
|
|
else
|
|
return [](u8 left, u8 right) -> bool {
|
|
if (std::isupper(left))
|
|
left = std::tolower(left);
|
|
if (std::isupper(right))
|
|
right = std::tolower(right);
|
|
|
|
return left == right;
|
|
};
|
|
}();
|
|
|
|
|
|
while (true) {
|
|
task.update(progress);
|
|
|
|
occurrence = std::search(reader.begin(), reader.end(), std::default_searcher(bytes.begin(), bytes.end(), searchPredicate));
|
|
if (occurrence == reader.end())
|
|
break;
|
|
|
|
auto address = occurrence.getAddress();
|
|
reader.seek(address + 1);
|
|
results.push_back(Occurrence{ Region { .address=address, .size=bytes.size() }, endian, decodeType, false, {} });
|
|
progress = address - searchRegion.getStartAddress();
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
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 {
|
|
.minLength = settings.minLength,
|
|
.nullTermination = settings.nullTermination,
|
|
.type = settings.type,
|
|
.lowerCaseLetters = true,
|
|
.upperCaseLetters = true,
|
|
.numbers = true,
|
|
.underscores = true,
|
|
.symbols = true,
|
|
.spaces = true,
|
|
.lineFeeds = true
|
|
});
|
|
|
|
std::vector<Occurrence> result;
|
|
boost::regex regex(settings.pattern);
|
|
for (const auto &occurrence : stringOccurrences) {
|
|
std::string string(occurrence.region.getSize(), '\x00');
|
|
provider->read(occurrence.region.getStartAddress(), string.data(), occurrence.region.getSize());
|
|
|
|
task.update();
|
|
|
|
if (settings.fullMatch) {
|
|
if (boost::regex_match(string, regex))
|
|
result.push_back(occurrence);
|
|
} else {
|
|
if (boost::regex_search(string, regex))
|
|
result.push_back(occurrence);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
|
|
auto reader = prv::ProviderReader(provider);
|
|
reader.seek(searchRegion.getStartAddress());
|
|
reader.setEndAddress(searchRegion.getEndAddress());
|
|
|
|
const size_t patternSize = settings.pattern.getSize();
|
|
|
|
if (settings.alignment == 1) {
|
|
u32 matchedBytes = 0;
|
|
for (auto it = reader.begin(); it < reader.end(); it += 1) {
|
|
auto byte = *it;
|
|
|
|
task.update(it.getAddress());
|
|
if (settings.pattern.matchesByte(byte, matchedBytes)) {
|
|
matchedBytes++;
|
|
if (matchedBytes == settings.pattern.getSize()) {
|
|
auto occurrenceAddress = it.getAddress() - (patternSize - 1);
|
|
|
|
results.push_back(Occurrence { Region { .address=occurrenceAddress, .size=patternSize }, std::endian::native, Occurrence::DecodeType::Binary, false, {} });
|
|
it.setAddress(occurrenceAddress);
|
|
matchedBytes = 0;
|
|
}
|
|
} else {
|
|
if (matchedBytes > 0)
|
|
it -= matchedBytes;
|
|
matchedBytes = 0;
|
|
}
|
|
}
|
|
} else {
|
|
std::vector<u8> data(patternSize);
|
|
for (u64 address = searchRegion.getStartAddress(); address < searchRegion.getEndAddress(); address += settings.alignment) {
|
|
reader.read(address, data.data(), data.size());
|
|
|
|
task.update(address);
|
|
|
|
bool match = true;
|
|
for (u32 i = 0; i < patternSize; i++) {
|
|
if (!settings.pattern.matchesByte(data[i], i)) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match)
|
|
results.push_back(Occurrence { Region { .address=address, .size=patternSize }, std::endian::native, Occurrence::DecodeType::Binary, false, {} });
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
template<typename T> T convert_signed_integer( T value, size_t size ) {
|
|
|
|
switch (size) {
|
|
case 1: return static_cast<T>(static_cast<i8>(value));
|
|
case 2: return static_cast<T>(static_cast<i16>(value));
|
|
case 4: return static_cast<T>(static_cast<i32>(value));
|
|
case 8: return static_cast<T>(static_cast<i64>(value));
|
|
default: return value;
|
|
}
|
|
}
|
|
|
|
std::vector<hex::ContentRegistry::DataFormatter::impl::FindOccurrence> ViewFind::searchValue(Task &task, prv::Provider *provider, Region searchRegion, const SearchSettings::Value &settings) {
|
|
std::vector<Occurrence> results;
|
|
|
|
auto reader = prv::ProviderReader(provider);
|
|
reader.seek(searchRegion.getStartAddress());
|
|
reader.setEndAddress(searchRegion.getEndAddress());
|
|
|
|
auto inputMin = settings.inputMin;
|
|
auto inputMax = settings.inputMax;
|
|
|
|
if (inputMax.empty())
|
|
inputMax = inputMin;
|
|
|
|
const auto [validMin, min, sizeMin] = parseNumericValueInput(inputMin, settings.type);
|
|
const auto [validMax, max, sizeMax] = parseNumericValueInput(inputMax, settings.type);
|
|
|
|
if (!validMin || !validMax || sizeMin != sizeMax)
|
|
return { };
|
|
|
|
const auto size = sizeMin;
|
|
|
|
const auto advance = settings.aligned ? size : 1;
|
|
|
|
for (u64 address = searchRegion.getStartAddress(); address < searchRegion.getEndAddress(); address += advance) {
|
|
task.update(address);
|
|
|
|
auto result = std::visit([&]<typename T>(T) {
|
|
using DecayedType = std::remove_cvref_t<std::decay_t<T>>;
|
|
|
|
auto minValue = std::get<DecayedType>(min);
|
|
auto maxValue = std::get<DecayedType>(max);
|
|
|
|
DecayedType value = 0;
|
|
reader.read(address, reinterpret_cast<u8*>(&value), size);
|
|
value = hex::changeEndianness(value, size, settings.endian);
|
|
if constexpr (std::signed_integral<DecayedType>)
|
|
value = convert_signed_integer<DecayedType>(value, size);
|
|
|
|
return value >= minValue && value <= maxValue;
|
|
}, min);
|
|
|
|
if (result) {
|
|
Occurrence::DecodeType decodeType = [&]{
|
|
switch (settings.type) {
|
|
using enum SearchSettings::Value::Type;
|
|
using enum Occurrence::DecodeType;
|
|
|
|
case U8:
|
|
case U16:
|
|
case U32:
|
|
case U64:
|
|
return Unsigned;
|
|
case I8:
|
|
case I16:
|
|
case I32:
|
|
case I64:
|
|
return Signed;
|
|
case F32:
|
|
return Float;
|
|
case F64:
|
|
return Double;
|
|
default:
|
|
return Binary;
|
|
}
|
|
}();
|
|
|
|
results.push_back(Occurrence { Region { .address=address, .size=size }, settings.endian, decodeType, false, {} });
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
std::vector<ViewFind::Occurrence> ViewFind::searchConstants(Task &task, prv::Provider* provider, Region searchRegion, const SearchSettings::Constants &settings) {
|
|
std::vector<Occurrence> results;
|
|
|
|
std::vector<ConstantGroup> constantGroups;
|
|
for (const auto &path : paths::Constants.read()) {
|
|
for (const auto &entry : std::fs::directory_iterator(path)) {
|
|
try {
|
|
constantGroups.emplace_back(entry.path());
|
|
} catch (const std::exception &e) {
|
|
ui::ToastError::open(fmt::format("Failed to load constant group from {}: {}", wolv::util::toUTF8String(entry.path()), e.what()));
|
|
}
|
|
}
|
|
}
|
|
|
|
auto reader = prv::ProviderReader(provider);
|
|
reader.seek(searchRegion.getStartAddress());
|
|
reader.setEndAddress(searchRegion.getEndAddress());
|
|
|
|
u64 constantCount = 0;
|
|
for (const auto &group : constantGroups) {
|
|
constantCount += group.getConstants().size();
|
|
}
|
|
task.setMaxValue(constantCount * searchRegion.getSize());
|
|
|
|
u64 progress = 0;
|
|
for (const auto &group : constantGroups) {
|
|
for (const auto &constant : group.getConstants()) {
|
|
const auto &pattern = constant.value;
|
|
const size_t patternSize = pattern.getSize();
|
|
if (settings.alignment == 1) {
|
|
u32 matchedBytes = 0;
|
|
for (auto it = reader.begin(); it < reader.end(); it += 1) {
|
|
auto byte = *it;
|
|
|
|
task.update(progress + it.getAddress());
|
|
if (pattern.matchesByte(byte, matchedBytes)) {
|
|
matchedBytes++;
|
|
if (matchedBytes == pattern.getSize()) {
|
|
auto occurrenceAddress = it.getAddress() - (patternSize - 1);
|
|
|
|
results.push_back(Occurrence {
|
|
Region { .address=occurrenceAddress, .size=patternSize },
|
|
std::endian::native,
|
|
Occurrence::DecodeType::ASCII,
|
|
false,
|
|
fmt::format("[{}] {}", group.getName(), constant.name)
|
|
});
|
|
it.setAddress(occurrenceAddress);
|
|
matchedBytes = 0;
|
|
}
|
|
} else {
|
|
if (matchedBytes > 0)
|
|
it -= matchedBytes;
|
|
matchedBytes = 0;
|
|
}
|
|
}
|
|
} else {
|
|
std::vector<u8> data(patternSize);
|
|
for (u64 address = searchRegion.getStartAddress(); address < searchRegion.getEndAddress(); address += settings.alignment) {
|
|
reader.read(address, data.data(), data.size());
|
|
|
|
task.update(address);
|
|
|
|
bool match = true;
|
|
for (u32 i = 0; i < patternSize; i++) {
|
|
if (!pattern.matchesByte(data[i], i)) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match)
|
|
results.push_back(Occurrence {
|
|
Region { .address=address, .size=patternSize },
|
|
std::endian::native,
|
|
Occurrence::DecodeType::ASCII,
|
|
false,
|
|
fmt::format("[] {}", group.getName(), constant.name)
|
|
});
|
|
}
|
|
}
|
|
|
|
progress += searchRegion.getSize();
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
void ViewFind::runSearch() {
|
|
Region searchRegion = m_searchSettings.region;
|
|
|
|
if (m_searchSettings.mode == SearchSettings::Mode::Strings) {
|
|
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_strings.name");
|
|
} else if (m_searchSettings.mode == SearchSettings::Mode::Sequence) {
|
|
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_specific_string.name");
|
|
} else if (m_searchSettings.mode == SearchSettings::Mode::Value) {
|
|
if (m_searchSettings.value.inputMin == "250" && m_searchSettings.value.inputMax == "1000")
|
|
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_numeric.name");
|
|
}
|
|
|
|
m_occurrenceTree->clear();
|
|
EventHighlightingChanged::post();
|
|
|
|
m_searchTask = TaskManager::createTask("hex.builtin.view.find.searching", searchRegion.getSize(), [this, settings = m_searchSettings, searchRegion](auto &task) {
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
switch (settings.mode) {
|
|
using enum SearchSettings::Mode;
|
|
case Strings:
|
|
m_foundOccurrences.get(provider) = searchStrings(task, provider, searchRegion, settings.strings);
|
|
break;
|
|
case Sequence:
|
|
m_foundOccurrences.get(provider) = searchSequence(task, provider, searchRegion, settings.bytes);
|
|
break;
|
|
case Regex:
|
|
m_foundOccurrences.get(provider) = searchRegex(task, provider, searchRegion, settings.regex);
|
|
break;
|
|
case BinaryPattern:
|
|
m_foundOccurrences.get(provider) = searchBinaryPattern(task, provider, searchRegion, settings.binaryPattern);
|
|
break;
|
|
case Value:
|
|
m_foundOccurrences.get(provider) = searchValue(task, provider, searchRegion, settings.value);
|
|
break;
|
|
case Constants:
|
|
m_foundOccurrences.get(provider) = searchConstants(task, provider, searchRegion, settings.constants);
|
|
break;
|
|
}
|
|
|
|
m_sortedOccurrences.get(provider).clear();
|
|
m_lastSelectedOccurrence = nullptr;
|
|
|
|
for (const auto &occurrence : m_foundOccurrences.get(provider))
|
|
m_occurrenceTree->insert({ .start=occurrence.region.getStartAddress(), .end=occurrence.region.getEndAddress() }, occurrence);
|
|
|
|
TaskManager::doLater([this, provider] {
|
|
EventHighlightingChanged::post();
|
|
m_settingsCollapsed.get(provider) = !m_foundOccurrences->empty();
|
|
});
|
|
});
|
|
|
|
m_decodeSettings = m_searchSettings;
|
|
m_foundOccurrences->clear();
|
|
m_sortedOccurrences->clear();
|
|
m_occurrenceTree->clear();
|
|
m_lastSelectedOccurrence = nullptr;
|
|
|
|
EventHighlightingChanged::post();
|
|
}
|
|
|
|
std::string ViewFind::decodeValue(prv::Provider *provider, const Occurrence &occurrence, size_t maxBytes) const {
|
|
std::vector<u8> bytes(std::min<size_t>(occurrence.region.getSize(), maxBytes));
|
|
provider->read(occurrence.region.getStartAddress(), bytes.data(), bytes.size());
|
|
|
|
std::string result;
|
|
switch (m_decodeSettings.mode) {
|
|
using enum SearchSettings::Mode;
|
|
|
|
case Value:
|
|
case Strings:
|
|
case Sequence:
|
|
case Regex:
|
|
{
|
|
switch (occurrence.decodeType) {
|
|
using enum Occurrence::DecodeType;
|
|
case Binary:
|
|
case ASCII:
|
|
result = hex::encodeByteString(bytes);
|
|
break;
|
|
case UTF8:
|
|
result = std::string(bytes.begin(), bytes.end());
|
|
result = wolv::util::replaceStrings(result, "\n", "");
|
|
result = wolv::util::replaceStrings(result, "\r", "");
|
|
break;
|
|
case UTF16:
|
|
for (size_t i = occurrence.endian == std::endian::little ? 0 : 1; i < bytes.size(); i += 2)
|
|
result += hex::encodeByteString({ bytes[i] });
|
|
break;
|
|
case Unsigned:
|
|
result = formatBytes<u64>(bytes, occurrence.endian);
|
|
break;
|
|
case Signed:
|
|
result = formatBytes<i64>(bytes, occurrence.endian);
|
|
break;
|
|
case Float:
|
|
result = formatBytes<float>(bytes, occurrence.endian);
|
|
break;
|
|
case Double:
|
|
result = formatBytes<double>(bytes, occurrence.endian);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case BinaryPattern:
|
|
result = hex::encodeByteString(bytes);
|
|
break;
|
|
case Constants:
|
|
result = occurrence.string;
|
|
break;
|
|
}
|
|
|
|
if (occurrence.region.getSize() > maxBytes)
|
|
result += "...";
|
|
|
|
return result;
|
|
}
|
|
|
|
void ViewFind::drawContextMenu(Occurrence &target, const std::string &value) {
|
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) {
|
|
ImGui::OpenPopup("FindContextMenu");
|
|
target.selected = true;
|
|
m_replaceBuffer.clear();
|
|
}
|
|
|
|
if (ImGui::BeginPopup("FindContextMenu")) {
|
|
if (ImGui::MenuItemEx("hex.builtin.view.find.context.copy"_lang, ICON_VS_COPY))
|
|
ImGui::SetClipboardText(value.c_str());
|
|
if (ImGui::MenuItemEx("hex.builtin.view.find.context.copy_demangle"_lang, ICON_VS_FILES))
|
|
ImGui::SetClipboardText(trace::demangle(value).c_str());
|
|
if (ImGui::BeginMenuEx("hex.builtin.view.find.context.replace"_lang, ICON_VS_REPLACE)) {
|
|
if (ImGui::BeginTabBar("##replace_tabs")) {
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.context.replace.hex"_lang)) {
|
|
ImGuiExt::InputTextIcon("##replace_input", ICON_VS_SYMBOL_NAMESPACE, m_replaceBuffer);
|
|
|
|
ImGui::BeginDisabled(m_replaceBuffer.empty());
|
|
if (ImGui::Button("hex.builtin.view.find.context.replace"_lang)) {
|
|
auto provider = ImHexApi::Provider::get();
|
|
auto bytes = parseHexString(m_replaceBuffer);
|
|
|
|
for (const auto &occurrence : *m_sortedOccurrences) {
|
|
if (occurrence.selected) {
|
|
size_t size = std::min<size_t>(occurrence.region.size, bytes.size());
|
|
provider->write(occurrence.region.getStartAddress(), bytes.data(), size);
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.context.replace.ascii"_lang)) {
|
|
ImGuiExt::InputTextIcon("##replace_input", ICON_VS_SYMBOL_KEY, m_replaceBuffer);
|
|
|
|
ImGui::BeginDisabled(m_replaceBuffer.empty());
|
|
if (ImGui::Button("hex.builtin.view.find.context.replace"_lang)) {
|
|
auto provider = ImHexApi::Provider::get();
|
|
auto bytes = decodeByteString(m_replaceBuffer);
|
|
|
|
for (const auto &occurrence : *m_sortedOccurrences) {
|
|
if (occurrence.selected) {
|
|
size_t size = std::min<size_t>(occurrence.region.size, bytes.size());
|
|
provider->write(occurrence.region.getStartAddress(), bytes.data(), size);
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
void ViewFind::drawContent() {
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
ImGui::BeginDisabled(m_searchTask.isRunning());
|
|
{
|
|
auto &collapsed = m_settingsCollapsed.get(provider);
|
|
ImGui::SetNextWindowScroll(ImVec2(0, 0));
|
|
if (ImGuiExt::BeginSubWindow("hex.ui.common.settings"_lang, &collapsed, collapsed ? ImVec2(0, 1) : ImVec2(0, 0))) {
|
|
ui::regionSelectionPicker(&m_searchSettings.region, provider, &m_searchSettings.range, false, true);
|
|
|
|
ImGui::SameLine();
|
|
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
|
ImGui::SameLine();
|
|
|
|
ImGui::BeginGroup();
|
|
if (ImGui::BeginTabBar("SearchMethods")) {
|
|
const std::array<std::string, 6> StringTypes = {
|
|
"hex.ui.common.encoding.ascii"_lang,
|
|
"hex.ui.common.encoding.utf8"_lang,
|
|
"hex.ui.common.encoding.utf16le"_lang,
|
|
"hex.ui.common.encoding.utf16be"_lang,
|
|
fmt::format("{} + {}", "hex.ui.common.encoding.ascii"_lang, "hex.ui.common.encoding.utf16le"_lang),
|
|
fmt::format("{} + {}", "hex.ui.common.encoding.ascii"_lang, "hex.ui.common.encoding.utf16be"_lang)
|
|
};
|
|
|
|
auto &mode = m_searchSettings.mode;
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.strings"_lang)) {
|
|
auto &settings = m_searchSettings.strings;
|
|
mode = SearchSettings::Mode::Strings;
|
|
|
|
ImGui::InputInt("hex.builtin.view.find.strings.min_length"_lang, &settings.minLength, 1, 1);
|
|
if (settings.minLength < 1)
|
|
settings.minLength = 1;
|
|
|
|
if (ImGui::BeginCombo("hex.ui.common.type"_lang, StringTypes[std::to_underlying(settings.type)].c_str())) {
|
|
for (size_t i = 0; i < StringTypes.size(); i++) {
|
|
auto type = static_cast<SearchSettings::StringType>(i);
|
|
|
|
if (ImGui::Selectable(StringTypes[i].c_str(), type == settings.type))
|
|
settings.type = type;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGuiExt::DimmedIconButton(ICON_VS_SYMBOL_STRING, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
ImGui::OpenPopup("##match_settings");
|
|
}
|
|
ImGui::SetItemTooltip("%s", "hex.builtin.view.find.strings.match_settings"_lang.get());
|
|
|
|
ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos(), ImGuiCond_Always);
|
|
if (ImGui::BeginPopup("##match_settings")) {
|
|
ImGui::Checkbox("hex.builtin.view.find.strings.null_term"_lang, &settings.nullTermination);
|
|
|
|
ImGuiExt::Header("hex.builtin.view.find.strings.chars"_lang);
|
|
ImGui::Checkbox(fmt::format("{} [a-z]", "hex.builtin.view.find.strings.lower_case"_lang.get()).c_str(), &settings.lowerCaseLetters);
|
|
ImGui::Checkbox(fmt::format("{} [A-Z]", "hex.builtin.view.find.strings.upper_case"_lang.get()).c_str(), &settings.upperCaseLetters);
|
|
ImGui::Checkbox(fmt::format("{} [0-9]", "hex.builtin.view.find.strings.numbers"_lang.get()).c_str(), &settings.numbers);
|
|
ImGui::Checkbox(fmt::format("{} [_]", "hex.builtin.view.find.strings.underscores"_lang.get()).c_str(), &settings.underscores);
|
|
ImGui::Checkbox(fmt::format("{} [!\"#$%...]", "hex.builtin.view.find.strings.symbols"_lang.get()).c_str(), &settings.symbols);
|
|
ImGui::Checkbox(fmt::format(R"({} [ \f\t\v])", "hex.builtin.view.find.strings.spaces"_lang.get()).c_str(), &settings.spaces);
|
|
ImGui::Checkbox(fmt::format("{} [\\r\\n]", "hex.builtin.view.find.strings.line_feeds"_lang.get()).c_str(), &settings.lineFeeds);
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
m_settingsValid = true;
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.sequences"_lang)) {
|
|
auto &settings = m_searchSettings.bytes;
|
|
|
|
mode = SearchSettings::Mode::Sequence;
|
|
|
|
ImGuiExt::InputTextIconHint("hex.ui.common.value"_lang, ICON_VS_SYMBOL_KEY, "String", settings.sequence);
|
|
|
|
if (ImGui::BeginCombo("hex.ui.common.type"_lang, StringTypes[std::to_underlying(settings.type)].c_str())) {
|
|
for (size_t i = 0; i < StringTypes.size() - 2; i++) {
|
|
auto type = static_cast<SearchSettings::StringType>(i);
|
|
|
|
if (ImGui::Selectable(StringTypes[i].c_str(), type == settings.type))
|
|
settings.type = type;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
ImGui::Checkbox("hex.builtin.view.find.sequences.ignore_case"_lang, &settings.ignoreCase);
|
|
|
|
m_settingsValid = !settings.sequence.empty() && !hex::decodeByteString(settings.sequence).empty();
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.regex"_lang)) {
|
|
auto &settings = m_searchSettings.regex;
|
|
|
|
mode = SearchSettings::Mode::Regex;
|
|
|
|
ImGui::InputInt("hex.builtin.view.find.strings.min_length"_lang, &settings.minLength, 1, 1);
|
|
if (settings.minLength < 1)
|
|
settings.minLength = 1;
|
|
|
|
if (ImGui::BeginCombo("hex.ui.common.type"_lang, StringTypes[std::to_underlying(settings.type)].c_str())) {
|
|
for (size_t i = 0; i < StringTypes.size(); i++) {
|
|
auto type = static_cast<SearchSettings::StringType>(i);
|
|
|
|
if (ImGui::Selectable(StringTypes[i].c_str(), type == settings.type))
|
|
settings.type = type;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
ImGui::Checkbox("hex.builtin.view.find.strings.null_term"_lang, &settings.nullTermination);
|
|
|
|
ImGui::NewLine();
|
|
|
|
ImGuiExt::InputTextIconHint("hex.builtin.view.find.regex.pattern"_lang, ICON_VS_REGEX, "[A-Za-z]{2}\\d{3}", settings.pattern);
|
|
|
|
try {
|
|
boost::regex regex(settings.pattern);
|
|
m_settingsValid = true;
|
|
} catch (const boost::regex_error &) {
|
|
m_settingsValid = false;
|
|
}
|
|
|
|
if (settings.pattern.empty())
|
|
m_settingsValid = false;
|
|
|
|
ImGui::Checkbox("hex.builtin.view.find.regex.full_match"_lang, &settings.fullMatch);
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.binary_pattern"_lang)) {
|
|
auto &settings = m_searchSettings.binaryPattern;
|
|
|
|
mode = SearchSettings::Mode::BinaryPattern;
|
|
|
|
if (ImGuiExt::InputTextIconHint("hex.builtin.view.find.binary_pattern"_lang, ICON_VS_SYMBOL_NAMESPACE, "AA BB ?? ?D \"XYZ\" u32be(1234)", settings.input)) {
|
|
settings.pattern = hex::BinaryPattern(settings.input);
|
|
}
|
|
|
|
constexpr static u32 min = 1, max = 0x1000;
|
|
ImGui::SliderScalar("hex.builtin.view.find.binary_pattern.alignment"_lang, ImGuiDataType_U32, &settings.alignment, &min, &max);
|
|
|
|
m_settingsValid = settings.pattern.isValid() && settings.alignment > 0;
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.value"_lang)) {
|
|
auto &settings = m_searchSettings.value;
|
|
|
|
mode = SearchSettings::Mode::Value;
|
|
|
|
bool edited = false;
|
|
|
|
if (settings.range) {
|
|
if (ImGuiExt::InputTextIcon("hex.builtin.view.find.value.min"_lang, ICON_VS_SYMBOL_NUMERIC, settings.inputMin)) edited = true;
|
|
if (ImGuiExt::InputTextIcon("hex.builtin.view.find.value.max"_lang, ICON_VS_SYMBOL_NUMERIC, settings.inputMax)) edited = true;
|
|
} else {
|
|
if (ImGuiExt::InputTextIcon("hex.ui.common.value"_lang, ICON_VS_SYMBOL_NUMERIC, settings.inputMin)) {
|
|
edited = true;
|
|
settings.inputMax = settings.inputMin;
|
|
}
|
|
|
|
ImGui::BeginDisabled();
|
|
ImGuiExt::InputTextIcon("##placeholder_value", ICON_VS_SYMBOL_NUMERIC, settings.inputMax);
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
if (ImGui::Checkbox("hex.builtin.view.find.value.range"_lang, &settings.range)) {
|
|
settings.inputMax = settings.inputMin;
|
|
}
|
|
ImGui::NewLine();
|
|
|
|
constexpr static std::array InputTypes = {
|
|
"hex.ui.common.type.u8"_lang,
|
|
"hex.ui.common.type.u16"_lang,
|
|
"hex.ui.common.type.u32"_lang,
|
|
"hex.ui.common.type.u64"_lang,
|
|
"hex.ui.common.type.i8"_lang,
|
|
"hex.ui.common.type.i16"_lang,
|
|
"hex.ui.common.type.i32"_lang,
|
|
"hex.ui.common.type.i64"_lang,
|
|
"hex.ui.common.type.f32"_lang,
|
|
"hex.ui.common.type.f64"_lang
|
|
};
|
|
|
|
if (ImGui::BeginCombo("hex.ui.common.type"_lang, InputTypes[std::to_underlying(settings.type)].get())) {
|
|
for (size_t i = 0; i < InputTypes.size(); i++) {
|
|
auto type = static_cast<SearchSettings::Value::Type>(i);
|
|
|
|
if (ImGui::Selectable(InputTypes[i].get(), type == settings.type)) {
|
|
settings.type = type;
|
|
edited = true;
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
{
|
|
int selection = [&] {
|
|
switch (settings.endian) {
|
|
default:
|
|
case std::endian::little: return 0;
|
|
case std::endian::big: return 1;
|
|
}
|
|
}();
|
|
|
|
std::array options = { "hex.ui.common.little"_lang, "hex.ui.common.big"_lang };
|
|
if (ImGui::SliderInt("hex.ui.common.endian"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) {
|
|
edited = true;
|
|
switch (selection) {
|
|
default:
|
|
case 0: settings.endian = std::endian::little; break;
|
|
case 1: settings.endian = std::endian::big; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::Checkbox("hex.builtin.view.find.value.aligned"_lang, &settings.aligned);
|
|
|
|
if (edited) {
|
|
auto [minValid, min, minSize] = parseNumericValueInput(settings.inputMin, settings.type);
|
|
auto [maxValid, max, maxSize] = parseNumericValueInput(settings.inputMax, settings.type);
|
|
|
|
m_settingsValid = minValid && maxValid && minSize == maxSize;
|
|
}
|
|
|
|
if (settings.inputMin.empty())
|
|
m_settingsValid = false;
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("hex.builtin.view.find.constants"_lang)) {
|
|
auto &settings = m_searchSettings.constants;
|
|
|
|
mode = SearchSettings::Mode::Constants;
|
|
|
|
constexpr static u32 min = 1, max = 0x1000;
|
|
ImGui::SliderScalar("hex.builtin.view.find.binary_pattern.alignment"_lang, ImGuiDataType_U32, &settings.alignment, &min, &max);
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
ImGui::EndGroup();
|
|
}
|
|
ImGuiExt::EndSubWindow();
|
|
|
|
ImGui::BeginDisabled(!m_settingsValid);
|
|
{
|
|
if (ImGuiExt::DimmedIconButton(ICON_VS_SEARCH, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
this->runSearch();
|
|
}
|
|
ImGui::SetItemTooltip("%s", "hex.builtin.view.find.search"_lang.get());
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::BeginDisabled(m_foundOccurrences->empty());
|
|
{
|
|
if (ImGuiExt::DimmedIconButton(ICON_VS_SEARCH_STOP, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
m_foundOccurrences->clear();
|
|
m_sortedOccurrences->clear();
|
|
m_occurrenceTree->clear();
|
|
m_lastSelectedOccurrence = nullptr;
|
|
|
|
EventHighlightingChanged::post();
|
|
}
|
|
ImGui::SetItemTooltip("%s", "hex.builtin.view.find.search.reset"_lang.get());
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGuiExt::TextFormatted("hex.builtin.view.find.search.entries"_lang, m_foundOccurrences->size());
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
|
|
ImGui::Separator();
|
|
ImGui::NewLine();
|
|
|
|
auto &currOccurrences = *m_sortedOccurrences;
|
|
|
|
ImGui::PushItemWidth(-30_scaled);
|
|
auto prevFilterLength = m_currFilter->length();
|
|
if (ImGuiExt::InputTextIcon("##filter", ICON_VS_FILTER, *m_currFilter)) {
|
|
if (prevFilterLength > m_currFilter->length())
|
|
*m_sortedOccurrences = *m_foundOccurrences;
|
|
|
|
if (m_filterTask.isRunning())
|
|
m_filterTask.interrupt();
|
|
|
|
static std::mutex mutex;
|
|
std::scoped_lock lock(mutex);
|
|
|
|
if (!m_currFilter->empty()) {
|
|
m_filterTask = TaskManager::createTask("hex.builtin.task.filtering_data", currOccurrences.size(), [this, provider, &currOccurrences, filter = m_currFilter.get(provider)](Task &task) {
|
|
std::scoped_lock lock(mutex);
|
|
|
|
u64 progress = 0;
|
|
std::erase_if(currOccurrences, [this, provider, &task, &progress, &filter](const auto ®ion) {
|
|
task.update(progress);
|
|
progress += 1;
|
|
|
|
return !hex::containsIgnoreCase(this->decodeValue(provider, region), filter);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
ImGui::PopItemWidth();
|
|
|
|
ImGui::SameLine();
|
|
|
|
const auto startPos = ImGui::GetCursorPos();
|
|
ImGui::BeginDisabled(m_sortedOccurrences->empty());
|
|
if (ImGuiExt::DimmedIconButton(ICON_VS_EXPORT, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
ImGui::OpenPopup("ExportResults");
|
|
}
|
|
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)))) {
|
|
ImGui::TableSetupScrollFreeze(0, 1);
|
|
ImGui::TableSetupColumn("hex.ui.common.offset"_lang, 0, -1, ImGui::GetID("offset"));
|
|
ImGui::TableSetupColumn("hex.ui.common.size"_lang, 0, -1, ImGui::GetID("size"));
|
|
ImGui::TableSetupColumn("hex.ui.common.value"_lang, ImGuiTableColumnFlags_WidthStretch, -1, ImGui::GetID("value"));
|
|
|
|
auto sortSpecs = ImGui::TableGetSortSpecs();
|
|
|
|
if (m_sortedOccurrences->empty() && !m_foundOccurrences->empty()) {
|
|
currOccurrences = *m_foundOccurrences;
|
|
sortSpecs->SpecsDirty = true;
|
|
}
|
|
|
|
if (sortSpecs->SpecsDirty) {
|
|
std::ranges::stable_sort(currOccurrences, [this, &sortSpecs, provider](const Occurrence &left, const Occurrence &right) -> bool {
|
|
if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("offset")) {
|
|
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
|
|
return left.region.getStartAddress() < right.region.getStartAddress();
|
|
else
|
|
return left.region.getStartAddress() > right.region.getStartAddress();
|
|
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("size")) {
|
|
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
|
|
return left.region.getSize() < right.region.getSize();
|
|
else
|
|
return left.region.getSize() > right.region.getSize();
|
|
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("value")) {
|
|
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
|
|
return this->decodeValue(provider, left) < this->decodeValue(provider, right);
|
|
else
|
|
return this->decodeValue(provider, left) > this->decodeValue(provider, right);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
sortSpecs->SpecsDirty = false;
|
|
}
|
|
|
|
ImGui::TableHeadersRow();
|
|
|
|
ImGuiListClipper clipper;
|
|
clipper.Begin(currOccurrences.size(), ImGui::GetTextLineHeightWithSpacing());
|
|
|
|
while (clipper.Step()) {
|
|
for (size_t i = clipper.DisplayStart; i < std::min<size_t>(clipper.DisplayEnd, currOccurrences.size()); i++) {
|
|
auto &foundItem = currOccurrences[i];
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
|
|
ImGuiExt::TextFormatted("0x{:08X}", foundItem.region.getStartAddress());
|
|
ImGui::TableNextColumn();
|
|
ImGuiExt::TextFormatted("{}", hex::toByteString(foundItem.region.getSize()));
|
|
ImGui::TableNextColumn();
|
|
|
|
ImGui::PushID(i);
|
|
|
|
auto value = this->decodeValue(provider, foundItem, 256);
|
|
ImGuiExt::TextFormatted("{}", value);
|
|
ImGui::SameLine();
|
|
if (ImGui::Selectable("##line", foundItem.selected, ImGuiSelectableFlags_SpanAllColumns)) {
|
|
if (ImGui::GetIO().KeyShift && m_lastSelectedOccurrence != nullptr) {
|
|
for (auto start = std::min(&foundItem, m_lastSelectedOccurrence.get(provider)); start <= std::max(&foundItem, m_lastSelectedOccurrence.get(provider)); start += 1)
|
|
start->selected = true;
|
|
|
|
} else if (ImGui::GetIO().KeyCtrl) {
|
|
foundItem.selected = !foundItem.selected;
|
|
} else {
|
|
for (auto &occurrence : *m_sortedOccurrences)
|
|
occurrence.selected = false;
|
|
foundItem.selected = true;
|
|
ImHexApi::HexEditor::setSelection(foundItem.region.getStartAddress(), foundItem.region.getSize());
|
|
}
|
|
|
|
m_lastSelectedOccurrence = &foundItem;
|
|
}
|
|
drawContextMenu(foundItem, value);
|
|
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
clipper.End();
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void ViewFind::drawHelpText() {
|
|
ImGuiExt::TextFormattedWrapped("This view lets you search for all occurrences of a specific pattern in the opened data source.");
|
|
ImGui::NewLine();
|
|
ImGuiExt::TextFormattedWrapped(
|
|
"- Strings: Search for all strings matching the specified criteria.\n"
|
|
"- Sequences: Search for a specific string character sequence in various encodings.\n"
|
|
"- Regex: Search for all strings matching the specified regular expression.\n"
|
|
"- Binary Pattern: Search for a specific byte pattern with wildcards.\n"
|
|
"- Numeric Value: Search for numeric values within a specified range in various formats."
|
|
);
|
|
}
|
|
|
|
}
|