Files
imhex/plugins/builtin/source/content/views/view_find.cpp
paxcut a4c0d38c53 fix: searching for negative values returns nothing. (#2306)
This pr aims at fixing for negative values in advanced search for
numerical values. For a simple example try searching for -1 for int32_t
which is 0xFFFFFFFF. With the changes you can now find -1 for 1,2,4 or 8
byte integers.

Internal types are bigger than or equal to the types selected in the
options. Search keys are converted to the bigger type, but the values
read from the input file are not. This works ok for positive numbers,
but for negatives it needs some casting.

The casting is performed inside a newly added function that takes the
value returned by read, the size in bytes of the selected type in the
options and a template argument for the 64 bit type the value is stored
into.

I have tested positive and negative values for several different sizes
of signed integers. Also tested unsigned integers both in the low range
(near lowest limit) and in the high range (near largest possible value
for that type)
2025-06-16 14:57:53 -07:00

1122 lines
49 KiB
C++

#include "content/views/view_find.hpp"
#include <hex/api/imhex_api.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/api/events/events_interaction.hpp>
#include <hex/providers/buffered_reader.hpp>
#include <fonts/vscode_icons.hpp>
#include <array>
#include <ranges>
#include <string>
#include <utility>
#include <content/helpers/demangle.hpp>
#include <boost/regex.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({ address, 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({ address, 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 = hex::plugin::builtin::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;
});
}
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 hex::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::copy(asciiResults.begin(), asciiResults.end(), std::back_inserter(results));
if (settings.type == ASCII_UTF16BE) {
newSettings.type = UTF16BE;
auto utf16Results = searchStrings(task, provider, searchRegion, newSettings);
std::copy(utf16Results.begin(), utf16Results.end(), std::back_inserter(results));
} else if (settings.type == ASCII_UTF16LE) {
newSettings.type = UTF16LE;
auto utf16Results = searchStrings(task, provider, searchRegion, newSettings);
std::copy(utf16Results.begin(), utf16Results.end(), 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 };
}();
i64 countedCharacters = 0;
u64 startAddress = reader.begin().getAddress();
u64 endAddress = reader.end().getAddress();
u64 progress = 0;
u64 codePointWidth = 0;
i8 remainingCharacters = 0;
for (u8 byte : reader) {
bool validChar =
(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'));
if (settings.type == UTF16LE) {
// Check if second byte of UTF-16 encoded string is 0x00
if (countedCharacters % 2 == 1)
validChar = byte == 0x00;
} else if (settings.type == UTF16BE) {
// Check if first byte of UTF-16 encoded string is 0x00
if (countedCharacters % 2 == 0)
validChar = byte == 0x00;
} else if (settings.type == UTF8) {
if ((byte & 0b1000'0000) == 0b0000'0000) {
// ASCII range
codePointWidth = 1;
remainingCharacters = 0;
validChar = true;
} else if ((byte & 0b1100'0000) == 0b1000'0000) {
// Continuation mark
if (remainingCharacters > 0) {
remainingCharacters -= 1;
validChar = true;
} else {
countedCharacters -= std::max<i64>(0, codePointWidth - (remainingCharacters + 1));
codePointWidth = 0;
remainingCharacters = 0;
validChar = false;
}
} else if ((byte & 0b1110'0000) == 0b1100'0000) {
// Two bytes
codePointWidth = 2;
remainingCharacters = codePointWidth - 1;
validChar = true;
} else if ((byte & 0b1111'0000) == 0b1110'0000) {
// Three bytes
codePointWidth = 3;
remainingCharacters = codePointWidth - 1;
validChar = true;
} else if ((byte & 0b1111'1000) == 0b1111'0000) {
// Four bytes
codePointWidth = 4;
remainingCharacters = codePointWidth - 1;
validChar = true;
} else {
validChar = false;
}
}
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 { startAddress, size_t(countedCharacters) }, decodeType, endian, 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, bytes.size() }, decodeType, endian, 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 { occurrenceAddress, patternSize }, Occurrence::DecodeType::Binary, std::endian::native, 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, patternSize }, Occurrence::DecodeType::Binary, std::endian::native, 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, size }, decodeType, settings.endian, false });
}
}
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;
}
m_sortedOccurrences.get(provider) = m_foundOccurrences.get(provider);
m_lastSelectedOccurrence = nullptr;
for (const auto &occurrence : m_foundOccurrences.get(provider))
m_occurrenceTree->insert({ occurrence.region.getStartAddress(), occurrence.region.getEndAddress() }, occurrence);
TaskManager::doLater([] {
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;
}
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(hex::plugin::builtin::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());
{
ui::regionSelectionPicker(&m_searchSettings.region, provider, &m_searchSettings.range, true, true);
ImGui::NewLine();
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,
hex::format("{} + {}", "hex.ui.common.encoding.ascii"_lang, "hex.ui.common.encoding.utf16le"_lang),
hex::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::NewLine();
if (ImGui::CollapsingHeader("hex.builtin.view.find.strings.match_settings"_lang)) {
ImGui::Checkbox("hex.builtin.view.find.strings.null_term"_lang, &settings.nullTermination);
ImGuiExt::Header("hex.builtin.view.find.strings.chars"_lang);
ImGui::Checkbox(hex::format("{} [a-z]", "hex.builtin.view.find.strings.lower_case"_lang.get()).c_str(), &settings.lowerCaseLetters);
ImGui::Checkbox(hex::format("{} [A-Z]", "hex.builtin.view.find.strings.upper_case"_lang.get()).c_str(), &settings.upperCaseLetters);
ImGui::Checkbox(hex::format("{} [0-9]", "hex.builtin.view.find.strings.numbers"_lang.get()).c_str(), &settings.numbers);
ImGui::Checkbox(hex::format("{} [_]", "hex.builtin.view.find.strings.underscores"_lang.get()).c_str(), &settings.underscores);
ImGui::Checkbox(hex::format("{} [!\"#$%...]", "hex.builtin.view.find.strings.symbols"_lang.get()).c_str(), &settings.symbols);
ImGui::Checkbox(hex::format("{} [ \\f\\t\\v]", "hex.builtin.view.find.strings.spaces"_lang.get()).c_str(), &settings.spaces);
ImGui::Checkbox(hex::format("{} [\\r\\n]", "hex.builtin.view.find.strings.line_feeds"_lang.get()).c_str(), &settings.lineFeeds);
}
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;
ImGuiExt::InputTextIconHint("hex.builtin.view.find.binary_pattern"_lang, ICON_VS_SYMBOL_NAMESPACE, "AA BB ?? ?D \"XYZ\"", 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);
settings.pattern = hex::BinaryPattern(settings.input);
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();
const std::array<std::string, 10> 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)].c_str())) {
for (size_t i = 0; i < InputTypes.size(); i++) {
auto type = static_cast<SearchSettings::Value::Type>(i);
if (ImGui::Selectable(InputTypes[i].c_str(), 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();
}
ImGui::EndTabBar();
}
ImGui::NewLine();
ImGui::BeginDisabled(!m_settingsValid);
{
if (ImGui::Button("hex.builtin.view.find.search"_lang)) {
this->runSearch();
m_decodeSettings = m_searchSettings;
}
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(m_foundOccurrences->empty());
{
if (ImGui::Button("hex.builtin.view.find.search.reset"_lang)) {
m_foundOccurrences->clear();
m_sortedOccurrences->clear();
m_occurrenceTree->clear();
m_lastSelectedOccurrence = nullptr;
EventHighlightingChanged::post();
}
}
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();
if (!m_currFilter->empty()) {
m_filterTask = TaskManager::createTask("hex.builtin.task.filtering_data", currOccurrences.size(), [this, provider, &currOccurrences](Task &task) {
u64 progress = 0;
std::erase_if(currOccurrences, [this, provider, &task, &progress](const auto &region) {
task.update(progress);
progress += 1;
return !hex::containsIgnoreCase(this->decodeValue(provider, region), m_currFilter.get(provider));
});
});
}
}
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, 0, -1, ImGui::GetID("value"));
auto sortSpecs = ImGui::TableGetSortSpecs();
if (sortSpecs->SpecsDirty) {
std::sort(currOccurrences.begin(), currOccurrences.end(), [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();
}
}
}