#include "content/views/view_find.hpp" #include #include #include #include #include #include #include #include namespace hex::plugin::builtin { ViewFind::ViewFind() : View("hex.builtin.view.find.name") { const static auto HighlightColor = [] { return (ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarPurple) & 0x00FFFFFF) | 0x70000000; }; const static auto FindRegion = [this](u64 address) -> std::optional { auto ®ions = this->m_foundRegions[ImHexApi::Provider::get()]; auto it = std::upper_bound(regions.begin(), regions.end(), address, [](u64 address, Region ®ion) { return address < region.getStartAddress(); }); if (it != regions.begin()) it--; if (it != regions.end() && it->getStartAddress() <= address && address <= it->getEndAddress()) return *it; else return std::nullopt; }; ImHexApi::HexEditor::addBackgroundHighlightingProvider([](u64 address, const u8* data, size_t size) -> std::optional { hex::unused(data, size); if (FindRegion(address).has_value()) return HighlightColor(); else return std::nullopt; }); ImHexApi::HexEditor::addTooltipProvider([this](u64 address, const u8* data, size_t size) { hex::unused(data, size); auto region = FindRegion(address); if (!region.has_value()) return; ImGui::BeginTooltip(); ImGui::PushID(®ion); if (ImGui::BeginTable("##tooltips", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); { const auto value = this->decodeValue(ImHexApi::Provider::get(), region.value()); ImGui::ColorButton("##color", ImColor(HighlightColor())); ImGui::SameLine(0, 10); ImGui::TextFormatted("{}", value); if (ImGui::GetIO().KeyShift) { ImGui::Indent(); if (ImGui::BeginTable("##extra_info", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{}: ", "hex.builtin.common.region"_lang.get()); ImGui::TableNextColumn(); ImGui::TextFormatted("[ 0x{:08X} - 0x{:08X} ]", region->getStartAddress(), region->getEndAddress()); auto demangledValue = llvm::demangle(value); if (value != demangledValue) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{}: ", "hex.builtin.view.find.demangled"_lang.get()); ImGui::TableNextColumn(); ImGui::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(); }); } std::vector ViewFind::searchStrings(prv::Provider *provider, hex::Region searchRegion, SearchSettings::Strings settings) { std::vector results; auto reader = prv::BufferedReader(provider); reader.seek(searchRegion.getStartAddress()); reader.setEndAddress(searchRegion.getEndAddress()); size_t countedCharacters = 0; u64 startAddress = reader.begin().getAddress(); for (u8 byte : reader) { bool validChar = (settings.m_lowerCaseLetters && std::islower(byte)) || (settings.m_upperCaseLetters && std::isupper(byte)) || (settings.m_numbers && std::isdigit(byte)) || (settings.m_spaces && std::isspace(byte)) || (settings.m_underscores && byte == '_') || (settings.m_symbols && std::ispunct(byte)) || (settings.m_lineFeeds && byte == '\n'); if (settings.type == SearchSettings::Strings::Type::UTF16LE) { // Check if second byte of UTF-16 encoded string is 0x00 if (countedCharacters % 2 == 1) validChar = byte == 0x00; } else if (settings.type == SearchSettings::Strings::Type::UTF16BE) { // Check if first byte of UTF-16 encoded string is 0x00 if (countedCharacters % 2 == 0) validChar = byte == 0x00; } if (validChar) countedCharacters++; else { if (countedCharacters >= size_t(settings.minLength)) { if (!(settings.nullTermination && byte != 0x00)) { results.push_back(Region { startAddress, countedCharacters }); } } startAddress += countedCharacters + 1; countedCharacters = 0; } } return results; } std::vector ViewFind::searchSequence(prv::Provider *provider, hex::Region searchRegion, SearchSettings::Bytes settings) { std::vector results; auto reader = prv::BufferedReader(provider); reader.seek(searchRegion.getStartAddress()); reader.setEndAddress(searchRegion.getEndAddress()); auto sequence = hex::decodeByteString(settings.sequence); auto occurrence = reader.begin(); while (true) { occurrence = std::search(reader.begin(), reader.end(), std::boyer_moore_horspool_searcher(sequence.begin(), sequence.end())); if (occurrence == reader.end()) break; reader.seek(occurrence.getAddress() + sequence.size()); results.push_back(Region { occurrence.getAddress(), sequence.size() }); } return results; } std::vector ViewFind::searchRegex(prv::Provider *provider, hex::Region searchRegion, SearchSettings::Regex settings) { auto stringRegions = searchStrings(provider, searchRegion, SearchSettings::Strings { .minLength = 1, .type = SearchSettings::Strings::Type::ASCII, .m_lowerCaseLetters = true, .m_upperCaseLetters = true, .m_numbers = true, .m_underscores = true, .m_symbols = true, .m_spaces = true, .m_lineFeeds = true }); std::vector result; std::regex regex(settings.pattern); for (const auto ®ion : stringRegions) { std::string string(region.getSize(), '\x00'); provider->read(region.getStartAddress(), string.data(), region.getSize()); if (std::regex_match(string, regex)) result.push_back(region); } return result; } void ViewFind::runSearch() { Region searchRegion = [this]{ if (this->m_searchSettings.range == 0 || !ImHexApi::HexEditor::isSelectionValid()) { auto provider = ImHexApi::Provider::get(); return Region { provider->getBaseAddress(), provider->getActualSize() }; } else { return ImHexApi::HexEditor::getSelection().value(); } }(); this->m_searchRunning = true; std::thread([this, settings = this->m_searchSettings, searchRegion]{ auto task = ImHexApi::Tasks::createTask("hex.builtin.view.find.searching", 0); auto provider = ImHexApi::Provider::get(); switch (settings.mode) { using enum SearchSettings::Mode; case Strings: this->m_foundRegions[provider] = searchStrings(provider, searchRegion, settings.strings); break; case Sequence: this->m_foundRegions[provider] = searchSequence(provider, searchRegion, settings.bytes); break; case Regex: this->m_foundRegions[provider] = searchRegex(provider, searchRegion, settings.regex); break; } this->m_sortedRegions = this->m_foundRegions; this->m_searchRunning = false; }).detach(); } std::string ViewFind::decodeValue(prv::Provider *provider, hex::Region region) { std::vector bytes(std::min(region.getSize(), 128)); provider->read(region.getStartAddress(), bytes.data(), bytes.size()); std::string result; switch (this->m_decodeSettings.mode) { using enum SearchSettings::Mode; case Strings: { auto &settings = this->m_decodeSettings.strings; switch (settings.type) { using enum SearchSettings::Strings::Type; case ASCII: result = hex::encodeByteString(bytes); break; case UTF16LE: for (size_t i = 0; i < bytes.size(); i += 2) result += hex::encodeByteString({ bytes[i] }); break; case UTF16BE: for (size_t i = 1; i < bytes.size(); i += 2) result += hex::encodeByteString({ bytes[i] }); break; } } break; case Sequence: case Regex: result = hex::encodeByteString(bytes); break; } return result; } static void drawContextMenu(const std::string &value) { if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { ImGui::OpenPopup("FindContextMenu"); } if (ImGui::BeginPopup("FindContextMenu")) { if (ImGui::MenuItem("hex.builtin.view.find.context.copy"_lang)) ImGui::SetClipboardText(value.c_str()); if (ImGui::MenuItem("hex.builtin.view.find.context.copy_demangle"_lang)) ImGui::SetClipboardText(llvm::demangle(value).c_str()); ImGui::EndPopup(); } } void ViewFind::drawContent() { if (ImGui::Begin(View::toWindowName("hex.builtin.view.find.name").c_str(), &this->getWindowOpenState())) { ImGui::BeginDisabled(this->m_searchRunning); { ImGui::Header("hex.builtin.view.find.range"_lang, true); ImGui::RadioButton("hex.builtin.view.find.range.entire_data"_lang, &this->m_searchSettings.range, 0); ImGui::RadioButton("hex.builtin.view.find.range.selection"_lang, &this->m_searchSettings.range, 1); ImGui::NewLine(); if (ImGui::BeginTabBar("SearchMethods")) { auto &mode = this->m_searchSettings.mode; if (ImGui::BeginTabItem("hex.builtin.view.find.strings"_lang)) { auto &settings = this->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; const std::array StringTypes = { "hex.builtin.common.encoding.ascii"_lang,"hex.builtin.common.encoding.utf16le"_lang, "hex.builtin.common.encoding.utf16be"_lang }; if (ImGui::BeginCombo("hex.builtin.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(); } if (ImGui::CollapsingHeader("hex.builtin.view.find.strings.match_settings"_lang)) { ImGui::Checkbox("hex.builtin.view.find.strings.null_term"_lang, &settings.nullTermination); ImGui::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.m_lowerCaseLetters); ImGui::Checkbox(hex::format("{} [A-Z]", "hex.builtin.view.find.strings.upper_case"_lang.get()).c_str(), &settings.m_upperCaseLetters); ImGui::Checkbox(hex::format("{} [0-9]", "hex.builtin.view.find.strings.numbers"_lang.get()).c_str(), &settings.m_numbers); ImGui::Checkbox(hex::format("{} [_]", "hex.builtin.view.find.strings.underscores"_lang.get()).c_str(), &settings.m_underscores); ImGui::Checkbox(hex::format("{} [!\"#$%...]", "hex.builtin.view.find.strings.symbols"_lang.get()).c_str(), &settings.m_symbols); ImGui::Checkbox(hex::format("{} [ ]", "hex.builtin.view.find.strings.spaces"_lang.get()).c_str(), &settings.m_spaces); ImGui::Checkbox(hex::format("{} [\\n]", "hex.builtin.view.find.strings.line_feeds"_lang.get()).c_str(), &settings.m_lineFeeds); } this->m_settingsValid = true; ImGui::EndTabItem(); } if (ImGui::BeginTabItem("hex.builtin.view.find.sequences"_lang)) { auto &settings = this->m_searchSettings.bytes; mode = SearchSettings::Mode::Sequence; ImGui::InputText("hex.builtin.common.value"_lang, settings.sequence); this->m_settingsValid = !settings.sequence.empty(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("hex.builtin.view.find.regex"_lang)) { auto &settings = this->m_searchSettings.regex; mode = SearchSettings::Mode::Regex; ImGui::InputText("hex.builtin.view.find.regex"_lang, settings.pattern); try { std::regex regex(settings.pattern); this->m_settingsValid = true; } catch (std::regex_error &e) { this->m_settingsValid = false; } if (settings.pattern.empty()) this->m_settingsValid = false; ImGui::EndTabItem(); } ImGui::EndTabBar(); } ImGui::NewLine(); ImGui::BeginDisabled(!this->m_settingsValid); { if (ImGui::Button("hex.builtin.view.find.search"_lang)) { this->runSearch(); this->m_decodeSettings = this->m_searchSettings; } } ImGui::EndDisabled(); } ImGui::EndDisabled(); ImGui::Separator(); ImGui::NewLine(); if (ImGui::BeginTable("##entries", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.common.offset"_lang, 0, -1, ImGui::GetID("offset")); ImGui::TableSetupColumn("hex.builtin.common.size"_lang, 0, -1, ImGui::GetID("size")); ImGui::TableSetupColumn("hex.builtin.common.value"_lang, 0, -1, ImGui::GetID("value")); auto provider = ImHexApi::Provider::get(); auto ®ions = this->m_sortedRegions[provider]; auto sortSpecs = ImGui::TableGetSortSpecs(); if (sortSpecs->SpecsDirty) { std::sort(regions.begin(), regions.end(), [this, &sortSpecs, provider](Region &left, Region &right) -> bool { if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("offset")) { if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending) return left.getStartAddress() > right.getStartAddress(); else return left.getStartAddress() < right.getStartAddress(); } else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("size")) { if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending) return left.getSize() > right.getSize(); else return left.getSize() < right.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(regions.size()); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { auto &foundItem = regions[i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("0x{:08X}", foundItem.getStartAddress()); ImGui::TableNextColumn(); ImGui::TextFormatted("{}", hex::toByteString(foundItem.getSize())); ImGui::TableNextColumn(); ImGui::PushID(i); auto value = this->decodeValue(provider, foundItem); ImGui::TextFormatted("{}", value); ImGui::SameLine(); if (ImGui::Selectable("##line", false, ImGuiSelectableFlags_SpanAllColumns)) ImHexApi::HexEditor::setSelection(foundItem.getStartAddress(), foundItem.getSize()); drawContextMenu(value); ImGui::PopID(); } } clipper.End(); ImGui::EndTable(); } } ImGui::End(); } }