feat: Added skipping sequences of repeating bytes (#2228)

This PR implements a neat little feature I missed - the ability to jump
to the next/previous differing byte, skipping the chunk of repeating
bytes. Very useful when you analyze a raw flash dump and want to skip
the large sections of `0x00`s/`0xFF`s.

Some implementation details worth validating:
- I wasn't sure what is the correct place to put the new menu entries
into. The possible candidates were `File -> Go to address...` and `Edit
-> Follow selection`. I chose the former, although the latter may be a
better fit since it already states that the action is related to the
selection. Overall, it may be a good moment to refine these menu entries
in general.
- I didn't add any tests since I'm not sure what is the project's policy
for those. Please let me know if I need to add some!
- I added the machine-generated translations for the new menu entries
which may be considered a questionable thing. Please let me know if
you're unhappy with those, I'll drop the commit.
 
Also, thanks for such a nice tool, I use it a lot and was glad to build
a new feature for it!
This commit is contained in:
Artem Garmash
2025-08-09 19:40:04 +02:00
committed by GitHub
parent d925c8216d
commit de7d549fc6
5 changed files with 162 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ add_imhex_plugin(
source/content/command_line_interface.cpp
source/content/communication_interface.cpp
source/content/data_inspector.cpp
source/content/differing_byte_searcher.cpp
source/content/pl_builtin_functions.cpp
source/content/pl_builtin_types.cpp
source/content/pl_pragmas.cpp

View File

@@ -0,0 +1,19 @@
#pragma once
#include <functional>
#include <hex/providers/provider.hpp>
#include <wolv/types.hpp>
namespace hex::plugin::builtin {
void findNextDifferingByte(
const std::function< u64(prv::Provider*) >& lastValidAddressProvider,
const std::function< bool(u64, u64) >& addressComparator,
const std::function< void(u64*) >& addressStepper,
bool *didFindNextValue,
bool *didReachEndAddress,
u64* foundAddress
);
bool canSearchForDifferingByte();
}

View File

@@ -862,6 +862,11 @@
"hex.builtin.view.hex_editor.menu.edit.set_base": "Set Base Address...",
"hex.builtin.view.hex_editor.menu.edit.set_page_size": "Set Page Size...",
"hex.builtin.view.hex_editor.menu.file.goto": "Go to address...",
"hex.builtin.view.hex_editor.menu.file.skip_until": "Skip Until",
"hex.builtin.view.hex_editor.menu.file.skip_until.previous_differing_byte": "Previous Differing Byte",
"hex.builtin.view.hex_editor.menu.file.skip_until.next_differing_byte": "Next Differing Byte",
"hex.builtin.view.hex_editor.menu.file.skip_until.beginning_reached": "No more differing bytes till the beginning of the file",
"hex.builtin.view.hex_editor.menu.file.skip_until.end_reached": "No more differing bytes till the end of the file",
"hex.builtin.view.hex_editor.menu.file.load_encoding_file": "Load custom encoding...",
"hex.builtin.view.hex_editor.menu.file.save": "Save",
"hex.builtin.view.hex_editor.menu.file.save_as": "Save As...",

View File

@@ -0,0 +1,52 @@
#include <content/differing_byte_searcher.hpp>
#include <hex/api/imhex_api.hpp>
namespace hex::plugin::builtin {
void findNextDifferingByte(
const std::function< u64(prv::Provider*) >& lastValidAddressProvider,
const std::function< bool(u64, u64) >& addressComparator,
const std::function< void(u64*) >& addressStepper,
bool *didFindNextValue,
bool *didReachEndAddress,
u64* foundAddress
) {
auto provider = ImHexApi::Provider::get();
if (provider == nullptr)
return;
const auto selection = ImHexApi::HexEditor::getSelection();
if (!selection.has_value())
return;
if (selection->getSize() != 1)
return;
auto currentAddress = selection->getStartAddress();
u8 givenValue = 0;
provider->read(currentAddress, &givenValue, 1);
u8 currentValue = 0;
*didFindNextValue = false;
*didReachEndAddress = false;
auto endAddress = lastValidAddressProvider(provider);
while (addressComparator(currentAddress, endAddress)) {
addressStepper(&currentAddress);
if (currentAddress == endAddress) {
*didReachEndAddress = true;
}
provider->read(currentAddress, &currentValue, 1);
if (currentValue != givenValue) {
*didFindNextValue = true;
*foundAddress = currentAddress;
break;
}
}
}
bool canSearchForDifferingByte() {
return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && ImHexApi::HexEditor::getSelection()->getSize() == 1;
}
}

View File

@@ -5,6 +5,8 @@
#include <hex/api/project_file_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <content/differing_byte_searcher.hpp>
#include <hex/api/events/events_provider.hpp>
#include <hex/api/events/requests_interaction.hpp>
#include <hex/api/events/requests_gui.hpp>
@@ -14,6 +16,7 @@
#include <hex/helpers/default_paths.hpp>
#include <hex/providers/buffered_reader.hpp>
#include <toasts/toast_notification.hpp>
#include <wolv/math_eval/math_evaluator.hpp>
@@ -1260,6 +1263,88 @@ namespace hex::plugin::builtin {
ImHexApi::Provider::isValid,
this);
/* Skip until */
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.skip_until" }, ICON_VS_DEBUG_STEP_OVER, 1610,
[]{},
canSearchForDifferingByte);
/* Skip until previous differing byte */
ContentRegistry::Interface::addMenuItem({
"hex.builtin.menu.file",
"hex.builtin.view.hex_editor.menu.file.skip_until",
"hex.builtin.view.hex_editor.menu.file.skip_until.previous_differing_byte"
},
ICON_VS_DEBUG_STEP_BACK,
1620,
CTRLCMD + Keys::LeftBracket,
[this] {
bool didFindNextValue = false;
bool didReachBeginning = false;
u64 foundAddress;
findNextDifferingByte(
[] (prv::Provider* provider) -> u64 {
return provider->getBaseAddress();
},
[] (u64 currentAddress, u64 endAddress) -> bool {
return currentAddress > endAddress;
},
[] (u64* currentAddress) {
(*currentAddress)--;
},
&didFindNextValue,
&didReachBeginning,
&foundAddress
);
if (didFindNextValue) {
ImHexApi::HexEditor::setSelection(foundAddress, 1);
}
if (!didFindNextValue && didReachBeginning) {
ui::ToastInfo::open("hex.builtin.view.hex_editor.menu.file.skip_until.beginning_reached"_lang);
}
},
canSearchForDifferingByte);
/* Skip until next differing byte */
ContentRegistry::Interface::addMenuItem({
"hex.builtin.menu.file",
"hex.builtin.view.hex_editor.menu.file.skip_until",
"hex.builtin.view.hex_editor.menu.file.skip_until.next_differing_byte"
},
ICON_VS_DEBUG_STEP_OVER,
1630,
CTRLCMD + Keys::RightBracket,
[this] {
bool didFindNextValue = false;
bool didReachEnd = false;
u64 foundAddress;
findNextDifferingByte(
[] (prv::Provider* provider) -> u64 {
return provider->getBaseAddress() + provider->getActualSize() - 1;
},
[] (u64 currentAddress, u64 endAddress) -> bool {
return currentAddress < endAddress;
},
[] (u64* currentAddress) {
(*currentAddress)++;
},
&didFindNextValue,
&didReachEnd,
&foundAddress
);
if (didFindNextValue) {
ImHexApi::HexEditor::setSelection(foundAddress, 1);
}
if (!didFindNextValue && didReachEnd) {
ui::ToastInfo::open("hex.builtin.view.hex_editor.menu.file.skip_until.end_reached"_lang);
}
},
canSearchForDifferingByte);
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1100, this);