#include "content/views/view_find.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 { 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 = 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 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 = { selection->getProvider()->getBaseAddress(), 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 static std::tuple, 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) value = std::stod(string, &processed); else if constexpr (std::signed_integral) 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::lowest() || value > std::numeric_limits::max()) return { false, { }, 0 }; return { true, value, sizeof(Type) }; } std::tuple, size_t> ViewFind::parseNumericValueInput(const std::string &input, SearchSettings::Value::Type type) { switch (type) { using enum SearchSettings::Value::Type; case U8: return parseNumericValue(input); case U16: return parseNumericValue(input); case U32: return parseNumericValue(input); case U64: return parseNumericValue(input); case I8: return parseNumericValue(input); case I16: return parseNumericValue(input); case I32: return parseNumericValue(input); case I64: return parseNumericValue(input); case F32: return parseNumericValue(input); case F64: return parseNumericValue(input); default: return { false, { }, 0 }; } } template static std::string formatBytes(const std::vector &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) value = T(hex::signExtend(bytes.size() * 8, value)); return fmt::format("{}", value); } std::vector ViewFind::searchStrings(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Strings &settings) { using enum SearchSettings::StringType; std::vector 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 { 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 { startAddress, size_t(countedCharacters) }, endian, decodeType, false, {} }); } } startAddress += countedCharacters + 1; countedCharacters = 0; progress = startAddress - searchRegion.getStartAddress(); } } return results; } std::vector ViewFind::searchSequence(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::Sequence &settings) { std::vector 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 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() }, endian, decodeType, false, {} }); progress = address - searchRegion.getStartAddress(); } return results; } std::vector 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 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 ViewFind::searchBinaryPattern(Task &task, prv::Provider *provider, hex::Region searchRegion, const SearchSettings::BinaryPattern &settings) { std::vector 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 }, std::endian::native, Occurrence::DecodeType::Binary, false, {} }); it.setAddress(occurrenceAddress); matchedBytes = 0; } } else { if (matchedBytes > 0) it -= matchedBytes; matchedBytes = 0; } } } else { std::vector 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 }, std::endian::native, Occurrence::DecodeType::Binary, false, {} }); } } return results; } template T convert_signed_integer( T value, size_t size ) { switch (size) { case 1: return static_cast(static_cast(value)); case 2: return static_cast(static_cast(value)); case 4: return static_cast(static_cast(value)); case 8: return static_cast(static_cast(value)); default: return value; } } std::vector ViewFind::searchValue(Task &task, prv::Provider *provider, Region searchRegion, const SearchSettings::Value &settings) { std::vector 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([&](T) { using DecayedType = std::remove_cvref_t>; auto minValue = std::get(min); auto maxValue = std::get(max); DecayedType value = 0; reader.read(address, reinterpret_cast(&value), size); value = hex::changeEndianness(value, size, settings.endian); if constexpr (std::signed_integral) value = convert_signed_integer(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 }, settings.endian, decodeType, false, {} }); } } return results; } std::vector ViewFind::searchConstants(Task &task, prv::Provider* provider, Region searchRegion, const SearchSettings::Constants &settings) { std::vector results; std::vector 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 { occurrenceAddress, 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 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, 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({ occurrence.region.getStartAddress(), 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 bytes(std::min(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(bytes, occurrence.endian); break; case Signed: result = formatBytes(bytes, occurrence.endian); break; case Float: result = formatBytes(bytes, occurrence.endian); break; case Double: result = formatBytes(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(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(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 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(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("{} [ \\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(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(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(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::lock_guard 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::lock_guard 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(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." ); } }