From 901b8f0424a8055b6ff050e0aa359512636b955c Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sun, 6 Nov 2022 12:19:12 +0100 Subject: [PATCH] sys: Refactored hex editor into its own reusable component --- lib/external/pattern_language | 2 +- plugins/builtin/CMakeLists.txt | 1 + .../include/content/helpers/hex_editor.hpp | 174 ++++ .../include/content/views/view_hex_editor.hpp | 128 +-- .../source/content/helpers/hex_editor.cpp | 865 ++++++++++++++++ .../source/content/views/view_hex_editor.cpp | 967 ++---------------- 6 files changed, 1136 insertions(+), 1001 deletions(-) create mode 100644 plugins/builtin/include/content/helpers/hex_editor.hpp create mode 100644 plugins/builtin/source/content/helpers/hex_editor.cpp diff --git a/lib/external/pattern_language b/lib/external/pattern_language index 78b4e491e..22cfecfbf 160000 --- a/lib/external/pattern_language +++ b/lib/external/pattern_language @@ -1 +1 @@ -Subproject commit 78b4e491e7c6033947e0d81e2361dc0881084e6d +Subproject commit 22cfecfbffbc6dee4879b9f9cfaedc943aca5bd6 diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index 3192f9bcd..6faecbf67 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -55,6 +55,7 @@ add_library(${PROJECT_NAME} SHARED source/content/helpers/math_evaluator.cpp source/content/helpers/pattern_drawer.cpp + source/content/helpers/hex_editor.cpp source/lang/de_DE.cpp source/lang/en_US.cpp diff --git a/plugins/builtin/include/content/helpers/hex_editor.hpp b/plugins/builtin/include/content/helpers/hex_editor.hpp new file mode 100644 index 000000000..560f1f259 --- /dev/null +++ b/plugins/builtin/include/content/helpers/hex_editor.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hex { + + class HexEditor { + public: + ~HexEditor(); + HexEditor(std::optional **selectionStart, std::optional **selectionEnd, float **scrollPosition); + void draw(); + + private: + enum class CellType { None, Hex, ASCII }; + + void drawCell(u64 address, u8 *data, size_t size, bool hovered, CellType cellType); + void drawSelectionFrame(u32 x, u32 y, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize) const; + void drawEditor(const ImVec2 &size); + void drawFooter(const ImVec2 &size); + + void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered); + std::optional applySelectionColor(u64 byteAddress, std::optional color); + + public: + void setSelection(const Region ®ion) { this->setSelection(region.getStartAddress(), region.getEndAddress()); } + void setSelection(u128 start, u128 end) { + if (!ImHexApi::Provider::isValid()) + return; + + auto provider = ImHexApi::Provider::get(); + + const size_t maxAddress = provider->getActualSize() + provider->getBaseAddress() - 1; + + this->m_selectionChanged = **this->m_selectionStart != start || **this->m_selectionEnd != end; + + **this->m_selectionStart = std::clamp(start, 0, maxAddress); + **this->m_selectionEnd = std::clamp(end, 0, maxAddress); + + if (this->m_selectionChanged) { + EventManager::post(this->getSelection()); + this->m_shouldModifyValue = true; + } + } + + [[nodiscard]] Region getSelection() const { + if (!isSelectionValid()) + return Region::Invalid(); + + const auto start = std::min((*this->m_selectionStart)->value(), (*this->m_selectionEnd)->value()); + const auto end = std::max((*this->m_selectionStart)->value(), (*this->m_selectionEnd)->value()); + const size_t size = end - start + 1; + + return { start, size }; + } + + [[nodiscard]] bool isSelectionValid() const { + return (*this->m_selectionStart)->has_value() && (*this->m_selectionEnd)->has_value(); + } + + void jumpToSelection(bool center = true) { + this->m_shouldJumpToSelection = true; + + if (center) + this->m_centerOnJump = true; + } + + void scrollToSelection() { + this->m_shouldScrollToSelection = true; + } + + void jumpIfOffScreen() { + this->m_shouldJumpWhenOffScreen = true; + } + + [[nodiscard]] u16 getBytesPerRow() const { + return this->m_bytesPerRow; + } + + void setBytesPerRow(u16 bytesPerRow) { + this->m_bytesPerRow = bytesPerRow; + } + + [[nodiscard]] u16 getVisibleRowCount() const { + return this->m_visibleRowCount; + } + + void setSelectionColor(color_t color) { + this->m_selectionColor = color; + } + + void enableUpperCaseHex(bool upperCaseHex) { + this->m_upperCaseHex = upperCaseHex; + } + + void enableGrayOutZeros(bool grayOutZeros) { + this->m_grayOutZero = grayOutZeros; + } + + void enableShowAscii(bool showAscii) { + this->m_showAscii = showAscii; + } + + void enableSyncScrolling(bool syncScrolling) { + this->m_syncScrolling = syncScrolling; + } + + void setByteCellPadding(u32 byteCellPadding) { + this->m_byteCellPadding = byteCellPadding; + } + + void setCharacterCellPadding(u32 characterCellPadding) { + this->m_characterCellPadding = characterCellPadding; + } + + void setCustomEncoding(EncodingFile encoding) { + this->m_currCustomEncoding = std::move(encoding); + } + + void forceUpdateScrollPosition() { + this->m_shouldUpdateScrollPosition = true; + } + + private: + std::optional **m_selectionStart; + std::optional **m_selectionEnd; + float **m_scrollPosition; + + u16 m_bytesPerRow = 16; + ContentRegistry::HexEditor::DataVisualizer *m_currDataVisualizer; + u32 m_grayZeroHighlighter = 0; + + bool m_shouldJumpToSelection = false; + bool m_centerOnJump = false; + bool m_shouldScrollToSelection = false; + bool m_shouldJumpWhenOffScreen = false; + bool m_shouldUpdateScrollPosition = false; + + bool m_selectionChanged = false; + + u16 m_visibleRowCount = 0; + + CellType m_editingCellType = CellType::None; + std::optional m_editingAddress; + bool m_shouldModifyValue = false; + bool m_enteredEditingMode = false; + bool m_shouldUpdateEditingValue = false; + std::vector m_editingBytes; + + color_t m_selectionColor = 0x00; + bool m_upperCaseHex = true; + bool m_grayOutZero = true; + bool m_showAscii = true; + bool m_syncScrolling = false; + u32 m_byteCellPadding = 0, m_characterCellPadding = 0; + + std::optional m_currCustomEncoding; + }; + +} \ No newline at end of file diff --git a/plugins/builtin/include/content/views/view_hex_editor.hpp b/plugins/builtin/include/content/views/view_hex_editor.hpp index 1ff33173e..753778bc9 100644 --- a/plugins/builtin/include/content/views/view_hex_editor.hpp +++ b/plugins/builtin/include/content/views/view_hex_editor.hpp @@ -7,85 +7,17 @@ #include #include - -#include -#include +#include namespace hex::plugin::builtin { class ViewHexEditor : public View { public: ViewHexEditor(); + ~ViewHexEditor(); void drawContent() override; - private: - void registerShortcuts(); - void registerEvents(); - void registerMenuItems(); - - enum class CellType { None, Hex, ASCII }; - - void drawCell(u64 address, u8 *data, size_t size, bool hovered, CellType cellType); - void drawPopup(); - void drawSelectionFrame(u32 x, u32 y, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize) const; - - public: - void setSelection(const Region ®ion) { this->setSelection(region.getStartAddress(), region.getEndAddress()); } - void setSelection(u128 start, u128 end) { - if (!ImHexApi::Provider::isValid()) - return; - - auto provider = ImHexApi::Provider::get(); - auto &data = ProviderExtraData::get(provider).editor; - - const size_t maxAddress = provider->getActualSize() + provider->getBaseAddress() - 1; - - this->m_selectionChanged = data.selectionStart != start || data.selectionEnd != end; - - data.selectionStart = std::clamp(start, 0, maxAddress); - data.selectionEnd = std::clamp(end, 0, maxAddress); - - if (this->m_selectionChanged) { - EventManager::post(this->getSelection()); - } - } - - [[nodiscard]] static Region getSelection() { - auto &data = ProviderExtraData::getCurrent().editor; - - if (!isSelectionValid()) - return Region::Invalid(); - - const auto start = std::min(*data.selectionStart, *data.selectionEnd); - const auto end = std::max(*data.selectionStart, *data.selectionEnd); - const size_t size = end - start + 1; - - return { start, size }; - } - - [[nodiscard]] static bool isSelectionValid() { - auto &data = ProviderExtraData::getCurrent().editor; - - return data.selectionStart.has_value() && data.selectionEnd.has_value(); - } - - void jumpToSelection(bool center = true) { - this->m_shouldJumpToSelection = true; - - if (center) - this->m_centerOnJump = true; - } - - void scrollToSelection() { - this->m_shouldScrollToSelection = true; - } - - void jumpIfOffScreen() { - this->m_shouldJumpWhenOffScreen = true; - } - - public: class Popup { public: virtual ~Popup() = default; @@ -111,46 +43,40 @@ namespace hex::plugin::builtin { this->m_currPopup.reset(); } - private: - void drawEditor(const ImVec2 &size); - void drawFooter(const ImVec2 &size); + bool isSelectionValid() { + return this->m_hexEditor->isSelectionValid(); + } - void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered); - std::optional applySelectionColor(u64 byteAddress, std::optional color); + Region getSelection() { + return this->m_hexEditor->getSelection(); + } + + void setSelection(const Region ®ion) { + this->m_hexEditor->setSelection(region); + } + + void setSelection(u128 start, u128 end) { + this->m_hexEditor->setSelection(start, end); + } + + void jumpToSelection() { + this->m_hexEditor->jumpToSelection(); + } private: - u16 m_bytesPerRow = 16; + void drawPopup(); - ContentRegistry::HexEditor::DataVisualizer *m_currDataVisualizer; + void registerShortcuts(); + void registerEvents(); + void registerMenuItems(); - bool m_shouldJumpToSelection = false; - bool m_centerOnJump = false; - bool m_shouldScrollToSelection = false; - bool m_shouldJumpWhenOffScreen = false; - bool m_shouldUpdateScrollPosition = false; - - bool m_selectionChanged = false; - - u16 m_visibleRowCount = 0; - - CellType m_editingCellType = CellType::None; - std::optional m_editingAddress; - bool m_shouldModifyValue = false; - bool m_enteredEditingMode = false; - bool m_shouldUpdateEditingValue = false; - std::vector m_editingBytes; - - color_t m_selectionColor = 0x00; - bool m_upperCaseHex = true; - bool m_grayOutZero = true; - bool m_showAscii = true; - bool m_syncScrolling = false; - u32 m_byteCellPadding = 0, m_characterCellPadding = 0; + std::unique_ptr m_hexEditor; bool m_shouldOpenPopup = false; std::unique_ptr m_currPopup; - std::optional m_currCustomEncoding; + std::optional *m_selectionStart, *m_selectionEnd; + float *m_scrollPosition; }; } \ No newline at end of file diff --git a/plugins/builtin/source/content/helpers/hex_editor.cpp b/plugins/builtin/source/content/helpers/hex_editor.cpp new file mode 100644 index 000000000..ff6509f2b --- /dev/null +++ b/plugins/builtin/source/content/helpers/hex_editor.cpp @@ -0,0 +1,865 @@ +#include + +#include +#include +#include + +#include +#include + +namespace hex { + + /* Data Visualizer */ + + class DataVisualizerAscii : public hex::ContentRegistry::HexEditor::DataVisualizer { + public: + DataVisualizerAscii() : DataVisualizer(1, 1) { } + + void draw(u64 address, const u8 *data, size_t size, bool upperCase) override { + hex::unused(address, upperCase); + + if (size == 1) { + const u8 c = data[0]; + if (std::isprint(c)) + ImGui::Text("%c", c); + else + ImGui::TextDisabled("."); + } + else + ImGui::TextDisabled("."); + } + + bool drawEditing(u64 address, u8 *data, size_t size, bool upperCase, bool startedEditing) override { + hex::unused(address, startedEditing, upperCase); + + if (size == 1) { + struct UserData { + u8 *data; + i32 maxChars; + + bool editingDone; + }; + + UserData userData = { + .data = data, + .maxChars = this->getMaxCharsPerCell(), + + .editingDone = false + }; + + ImGui::PushID(reinterpret_cast(address)); + char buffer[2] = { std::isprint(data[0]) ? char(data[0]) : '.', 0x00 }; + ImGui::InputText("##editing_input", buffer, 2, TextInputFlags | ImGuiInputTextFlags_CallbackEdit, [](ImGuiInputTextCallbackData *data) -> int { + auto &userData = *reinterpret_cast(data->UserData); + + if (data->BufTextLen >= userData.maxChars) { + userData.editingDone = true; + userData.data[0] = data->Buf[0]; + } + + return 0; + }, &userData); + ImGui::PopID(); + + return userData.editingDone || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Escape); + } + else + return false; + } + }; + + /* Hex Editor */ + + HexEditor::HexEditor(std::optional **selectionStart, std::optional **selectionEnd, float **scrollPosition) + : m_selectionStart(selectionStart), m_selectionEnd(selectionEnd), m_scrollPosition(scrollPosition) { + this->m_currDataVisualizer = ContentRegistry::HexEditor::impl::getVisualizers()["hex.builtin.visualizer.hexadecimal.8bit"]; + + this->m_grayZeroHighlighter = ImHexApi::HexEditor::addForegroundHighlightingProvider([this](u64 address, const u8 *data, size_t size, bool hasColor) -> std::optional { + hex::unused(address); + + if (hasColor) + return std::nullopt; + + if (!this->m_grayOutZero) + return std::nullopt; + + for (u32 i = 0; i < size; i++) + if (data[i] != 0x00) + return std::nullopt; + + return ImGui::GetColorU32(ImGuiCol_TextDisabled); + }); + + EventManager::subscribe(this, [this] { + { + auto bytesPerRow = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.bytes_per_row"); + + if (bytesPerRow.is_number()) + this->m_bytesPerRow = static_cast(bytesPerRow); + } + + { + auto ascii = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.ascii"); + + if (ascii.is_number()) + this->m_showAscii = static_cast(ascii); + } + + { + auto greyOutZeros = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.grey_zeros"); + + if (greyOutZeros.is_number()) + this->m_grayOutZero = static_cast(greyOutZeros); + } + + { + auto upperCaseHex = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.uppercase_hex"); + + if (upperCaseHex.is_number()) + this->m_upperCaseHex = static_cast(upperCaseHex); + } + + { + auto selectionColor = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.highlight_color"); + + if (selectionColor.is_number()) + this->m_selectionColor = static_cast(selectionColor); + } + + { + auto &visualizers = ContentRegistry::HexEditor::impl::getVisualizers(); + auto selectedVisualizer = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.visualizer"); + + if (selectedVisualizer.is_string() && visualizers.contains(selectedVisualizer)) + this->m_currDataVisualizer = visualizers[selectedVisualizer]; + else + this->m_currDataVisualizer = visualizers["hex.builtin.visualizer.hexadecimal.8bit"]; + } + + { + auto syncScrolling = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling"); + + if (syncScrolling.is_number()) + this->m_syncScrolling = static_cast(syncScrolling); + } + + { + auto padding = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding"); + + if (padding.is_number()) + this->m_byteCellPadding = static_cast(padding); + } + + { + auto padding = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding"); + + if (padding.is_number()) + this->m_characterCellPadding = static_cast(padding); + } + + }); + + } + + HexEditor::~HexEditor() { + ImHexApi::HexEditor::removeForegroundHighlightingProvider(this->m_grayZeroHighlighter); + EventManager::unsubscribe(this); + } + + constexpr static u16 getByteColumnSeparatorCount(u16 columnCount) { + return (columnCount - 1) / 8; + } + + constexpr static bool isColumnSeparatorColumn(u16 currColumn, u16 columnCount) { + return currColumn > 0 && (currColumn) < columnCount && ((currColumn) % 8) == 0; + } + + static std::optional queryBackgroundColor(u64 address, const u8 *data, size_t size) { + std::optional result; + for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getBackgroundHighlightingFunctions()) { + if (auto color = callback(address, data, size, result.has_value()); color.has_value()) + return color.value(); + } + + if (result.has_value()) + return result; + + for (const auto &[id, highlighting] : ImHexApi::HexEditor::impl::getBackgroundHighlights()) { + if (highlighting.getRegion().overlaps({ address, size })) + return highlighting.getColor(); + } + + return std::nullopt; + } + + static std::optional queryForegroundColor(u64 address, const u8 *data, size_t size) { + std::optional result; + for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getForegroundHighlightingFunctions()) { + if (auto color = callback(address, data, size, result.has_value()); color.has_value()) + result = color; + } + + if (result.has_value()) + return result; + + for (const auto &[id, highlighting] : ImHexApi::HexEditor::impl::getForegroundHighlights()) { + if (highlighting.getRegion().overlaps({ address, size })) + return highlighting.getColor(); + } + + return std::nullopt; + } + + std::optional HexEditor::applySelectionColor(u64 byteAddress, std::optional color) { + if (isSelectionValid()) { + auto selection = getSelection(); + + if (byteAddress >= selection.getStartAddress() && byteAddress <= selection.getEndAddress()) { + if (color.has_value()) + color = (ImAlphaBlendColors(color.value(), this->m_selectionColor)) & 0x00FFFFFF; + else + color = this->m_selectionColor; + } + } + + if (color.has_value()) + color = (*color & 0x00FFFFFF) | (this->m_selectionColor & 0xFF000000); + + return color; + } + + struct CustomEncodingData { + std::string displayValue; + size_t advance; + ImColor color; + }; + + static CustomEncodingData queryCustomEncodingData(const EncodingFile &encodingFile, u64 address) { + const auto longestSequence = encodingFile.getLongestSequence(); + + if (longestSequence == 0) + return { ".", 1, 0xFFFF8000 }; + + auto provider = ImHexApi::Provider::get(); + size_t size = std::min(longestSequence, provider->getActualSize() - address); + + std::vector buffer(size); + provider->read(address + provider->getBaseAddress() + provider->getCurrentPageAddress(), buffer.data(), size); + + const auto [decoded, advance] = encodingFile.getEncodingFor(buffer); + const ImColor color = [&decoded = decoded, &advance = advance]{ + if (decoded.length() == 1 && std::isalnum(decoded[0])) + return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarBlue); + else if (decoded.length() == 1 && advance == 1) + return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarRed); + else if (decoded.length() > 1 && advance == 1) + return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow); + else if (advance > 1) + return ImGui::GetColorU32(ImGuiCol_Text); + else + return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarBlue); + }(); + + return { std::string(decoded), advance, color }; + } + + static auto getCellPosition() { + return ImGui::GetCursorScreenPos() - ImGui::GetStyle().CellPadding; + } + + static void drawTooltip(u64 address, const u8 *data, size_t size) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, scaled(ImVec2(5, 5))); + + for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getTooltipFunctions()) { + callback(address, data, size); + } + + for (const auto &[id, tooltip] : ImHexApi::HexEditor::impl::getTooltips()) { + if (tooltip.getRegion().overlaps({ address, size })) { + ImGui::BeginTooltip(); + if (ImGui::BeginTable("##tooltips", 1, ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::ColorButton(tooltip.getValue().c_str(), ImColor(tooltip.getColor())); + ImGui::SameLine(0, 10); + ImGui::TextUnformatted(tooltip.getValue().c_str()); + + ImGui::PushStyleColor(ImGuiCol_TableRowBg, tooltip.getColor()); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, tooltip.getColor()); + ImGui::EndTable(); + ImGui::PopStyleColor(2); + } + ImGui::EndTooltip(); + } + } + + ImGui::PopStyleVar(); + } + + void HexEditor::drawCell(u64 address, u8 *data, size_t size, bool hovered, CellType cellType) { + static DataVisualizerAscii asciiVisualizer; + + auto provider = ImHexApi::Provider::get(); + + if (this->m_shouldUpdateEditingValue) { + this->m_shouldUpdateEditingValue = false; + + this->m_editingBytes.resize(size); + std::memcpy(this->m_editingBytes.data(), data, size); + } + + if (this->m_editingAddress != address || this->m_editingCellType != cellType) { + if (cellType == CellType::Hex) + this->m_currDataVisualizer->draw(address, data, size, this->m_upperCaseHex); + else + asciiVisualizer.draw(address, data, size, this->m_upperCaseHex); + + if (hovered && provider->isWritable()) { + // Enter editing mode when double-clicking a cell + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + this->m_editingAddress = address; + this->m_shouldModifyValue = false; + this->m_enteredEditingMode = true; + + this->m_editingBytes.resize(size); + std::memcpy(this->m_editingBytes.data(), data, size); + this->m_editingCellType = cellType; + } + } + } + else { + ImGui::SetKeyboardFocusHere(); + ImGui::SetNextFrameWantCaptureKeyboard(true); + + bool shouldExitEditingMode = true; + if (cellType == this->m_editingCellType && cellType == CellType::Hex) + shouldExitEditingMode = this->m_currDataVisualizer->drawEditing(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size(), this->m_upperCaseHex, this->m_enteredEditingMode); + else if (cellType == this->m_editingCellType && cellType == CellType::ASCII) + shouldExitEditingMode = asciiVisualizer.drawEditing(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size(), this->m_upperCaseHex, this->m_enteredEditingMode); + + if (shouldExitEditingMode || this->m_shouldModifyValue) { + + provider->write(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size()); + + if (!this->m_selectionChanged && !ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + auto nextEditingAddress = *this->m_editingAddress + this->m_currDataVisualizer->getBytesPerCell(); + this->setSelection(nextEditingAddress, nextEditingAddress); + + if (nextEditingAddress >= provider->getSize()) + this->m_editingAddress = std::nullopt; + else + this->m_editingAddress = nextEditingAddress; + } else { + this->m_editingAddress = std::nullopt; + } + + this->m_shouldModifyValue = false; + this->m_shouldUpdateEditingValue = true; + } + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !hovered && !this->m_enteredEditingMode) { + this->m_editingAddress = std::nullopt; + this->m_shouldModifyValue = false; + } + + if (!this->m_editingAddress.has_value()) + this->m_editingCellType = CellType::None; + } + } + + void HexEditor::drawSelectionFrame(u32 x, u32 y, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize) const { + if (!this->isSelectionValid()) return; + + const auto selection = getSelection(); + if (!Region { byteAddress, 1 }.isWithin(selection)) + return; + + const color_t SelectionFrameColor = ImGui::GetColorU32(ImGuiCol_Text); + + auto drawList = ImGui::GetWindowDrawList(); + + // Draw vertical line at the left of first byte and the start of the line + if (x == 0 || byteAddress == selection.getStartAddress()) + drawList->AddLine(cellPos, cellPos + ImVec2(0, cellSize.y), ImColor(SelectionFrameColor), 1.0F); + + // Draw vertical line at the right of the last byte and the end of the line + if (x == u16((this->m_bytesPerRow / bytesPerCell) - 1) || (byteAddress + bytesPerCell) > selection.getEndAddress()) + drawList->AddLine(cellPos + ImVec2(cellSize.x, -1), cellPos + cellSize, ImColor(SelectionFrameColor), 1.0F); + + // Draw horizontal line at the top of the bytes + if (y == 0 || (byteAddress - this->m_bytesPerRow) < selection.getStartAddress()) + drawList->AddLine(cellPos, cellPos + ImVec2(cellSize.x + 1, 0), ImColor(SelectionFrameColor), 1.0F); + + // Draw horizontal line at the bottom of the bytes + if ((byteAddress + this->m_bytesPerRow) > selection.getEndAddress()) + drawList->AddLine(cellPos + ImVec2(0, cellSize.y), cellPos + cellSize + ImVec2(1, 0), ImColor(SelectionFrameColor), 1.0F); + } + + void HexEditor::drawEditor(const ImVec2 &size) { + const float SeparatorColumWidth = 6_scaled; + const auto CharacterSize = ImGui::CalcTextSize("0"); + + const auto bytesPerCell = this->m_currDataVisualizer->getBytesPerCell(); + const u16 columnCount = this->m_bytesPerRow / bytesPerCell; + const auto byteColumnCount = columnCount + getByteColumnSeparatorCount(columnCount); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.5, 0)); + if (ImGui::BeginTable("##hex", 2 + byteColumnCount + 2 + 2 , ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible, size)) { + View::discardNavigationRequests(); + ImGui::TableSetupScrollFreeze(0, 2); + + // Row address column + ImGui::TableSetupColumn("hex.builtin.common.address"_lang); + ImGui::TableSetupColumn(""); + + // Byte columns + for (u16 i = 0; i < columnCount; i++) { + if (isColumnSeparatorColumn(i, columnCount)) + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, SeparatorColumWidth); + + ImGui::TableSetupColumn(hex::format(this->m_upperCaseHex ? "{:0{}X}" : "{:0{}x}", i * bytesPerCell, this->m_currDataVisualizer->getMaxCharsPerCell()).c_str(), ImGuiTableColumnFlags_WidthFixed, CharacterSize.x * this->m_currDataVisualizer->getMaxCharsPerCell() + 6 + this->m_byteCellPadding); + } + + // ASCII column + ImGui::TableSetupColumn(""); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, (CharacterSize.x + this->m_characterCellPadding) * this->m_bytesPerRow); + + // Custom encoding column + ImGui::TableSetupColumn(""); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + for (i32 i = 0; i < ImGui::TableGetColumnCount(); i++) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(ImGui::TableGetColumnName(i)); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + CharacterSize.y / 2); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + if (ImHexApi::Provider::isValid()) { + auto provider = ImHexApi::Provider::get(); + + std::pair validRegion = { Region::Invalid(), false }; + const auto isCurrRegionValid = [&validRegion, &provider](u64 address){ + auto &[currRegion, currRegionValid] = validRegion; + if (!Region{ address, 1 }.isWithin(currRegion)) { + validRegion = provider->getRegionValidity(address); + } + + return currRegionValid; + }; + + ImGuiListClipper clipper; + + clipper.Begin(std::ceil(provider->getSize() / (long double)(this->m_bytesPerRow)), CharacterSize.y); + while (clipper.Step()) { + this->m_visibleRowCount = clipper.DisplayEnd - clipper.DisplayStart; + + // Loop over rows + for (u64 y = u64(clipper.DisplayStart); y < u64(clipper.DisplayEnd); y++) { + + // Draw address column + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextFormatted(this->m_upperCaseHex ? "{:08X}: " : "{:08x}: ", y * this->m_bytesPerRow + provider->getBaseAddress() + provider->getCurrentPageAddress()); + ImGui::TableNextColumn(); + + const u8 validBytes = std::min(this->m_bytesPerRow, provider->getSize() - y * this->m_bytesPerRow); + + std::vector bytes(this->m_bytesPerRow, 0x00); + provider->read(y * this->m_bytesPerRow + provider->getBaseAddress() + provider->getCurrentPageAddress(), bytes.data(), validBytes); + + std::vector, std::optional>> cellColors; + { + for (u64 x = 0; x < std::ceil(float(validBytes) / bytesPerCell); x++) { + const u64 byteAddress = y * this->m_bytesPerRow + x * bytesPerCell + provider->getBaseAddress() + provider->getCurrentPageAddress(); + + const auto cellBytes = std::min(validBytes, bytesPerCell); + + // Query cell colors + if (x < std::ceil(float(validBytes) / bytesPerCell)) { + const auto foregroundColor = queryForegroundColor(byteAddress, &bytes[x * cellBytes], cellBytes); + const auto backgroundColor = queryBackgroundColor(byteAddress, &bytes[x * cellBytes], cellBytes); + + cellColors.emplace_back( + foregroundColor, + backgroundColor + ); + } else { + cellColors.emplace_back( + std::nullopt, + std::nullopt + ); + } + } + } + + // Draw byte columns + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(3, 0)); + + for (u64 x = 0; x < columnCount; x++) { + const u64 byteAddress = y * this->m_bytesPerRow + x * bytesPerCell + provider->getBaseAddress() + provider->getCurrentPageAddress(); + + ImGui::TableNextColumn(); + if (isColumnSeparatorColumn(x, columnCount)) + ImGui::TableNextColumn(); + + if (x < std::ceil(float(validBytes) / bytesPerCell)) { + auto cellStartPos = getCellPosition(); + auto cellSize = (CharacterSize * ImVec2(this->m_currDataVisualizer->getMaxCharsPerCell(), 1) + (ImVec2(3, 2) * ImGui::GetStyle().CellPadding) - ImVec2(1, 0) * ImGui::GetStyle().CellPadding) + ImVec2(1 + this->m_byteCellPadding, 0); + auto maxCharsPerCell = this->m_currDataVisualizer->getMaxCharsPerCell(); + + auto [foregroundColor, backgroundColor] = cellColors[x]; + + if (isColumnSeparatorColumn(x + 1, columnCount) && cellColors.size() > x + 1) { + auto separatorAddress = x + y * columnCount; + auto [nextForegroundColor, nextBackgroundColor] = cellColors[x + 1]; + if ((isSelectionValid() && getSelection().overlaps({ separatorAddress, 1 }) && getSelection().getEndAddress() != separatorAddress) || backgroundColor == nextBackgroundColor) + cellSize.x += SeparatorColumWidth + 1; + } + + if (y == u64(clipper.DisplayStart)) + cellSize.y -= (ImGui::GetStyle().CellPadding.y + 1); + + backgroundColor = applySelectionColor(byteAddress, backgroundColor); + + // Draw highlights and selection + if (backgroundColor.has_value()) { + auto drawList = ImGui::GetWindowDrawList(); + + // Draw background color + drawList->AddRectFilled(cellStartPos, cellStartPos + cellSize, backgroundColor.value()); + + // Draw frame around mouse selection + this->drawSelectionFrame(x, y, byteAddress, bytesPerCell, cellStartPos, cellSize); + } + + const bool cellHovered = ImGui::IsMouseHoveringRect(cellStartPos, cellStartPos + cellSize, false); + + this->handleSelection(byteAddress, bytesPerCell, &bytes[x * bytesPerCell], cellHovered); + + // Get byte foreground color + if (foregroundColor.has_value()) + ImGui::PushStyleColor(ImGuiCol_Text, *foregroundColor); + + // Draw cell content + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushItemWidth((CharacterSize * maxCharsPerCell).x); + if (isCurrRegionValid(byteAddress)) + this->drawCell(byteAddress, &bytes[x * bytesPerCell], bytesPerCell, cellHovered, CellType::Hex); + else + ImGui::TextFormatted("{}", std::string(maxCharsPerCell, '?')); + ImGui::PopItemWidth(); + ImGui::PopStyleVar(); + + if (foregroundColor.has_value()) + ImGui::PopStyleColor(); + } + } + ImGui::PopStyleVar(); + + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + // Draw ASCII column + if (this->m_showAscii) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); + if (ImGui::BeginTable("##ascii_column", this->m_bytesPerRow)) { + for (u64 x = 0; x < this->m_bytesPerRow; x++) + ImGui::TableSetupColumn(hex::format("##ascii_cell{}", x).c_str(), ImGuiTableColumnFlags_WidthFixed, CharacterSize.x + this->m_characterCellPadding); + + ImGui::TableNextRow(); + + for (u64 x = 0; x < this->m_bytesPerRow; x++) { + ImGui::TableNextColumn(); + + const u64 byteAddress = y * this->m_bytesPerRow + x + provider->getBaseAddress() + provider->getCurrentPageAddress(); + + const auto cellStartPos = getCellPosition(); + const auto cellSize = CharacterSize + ImVec2(this->m_characterCellPadding, 0); + + const bool cellHovered = ImGui::IsMouseHoveringRect(cellStartPos, cellStartPos + cellSize, true); + + if (x < validBytes) { + this->handleSelection(byteAddress, bytesPerCell, &bytes[x], cellHovered); + + auto [foregroundColor, backgroundColor] = cellColors[x / bytesPerCell]; + + backgroundColor = applySelectionColor(byteAddress, backgroundColor); + + // Draw highlights and selection + if (backgroundColor.has_value()) { + auto drawList = ImGui::GetWindowDrawList(); + + // Draw background color + drawList->AddRectFilled(cellStartPos, cellStartPos + cellSize, backgroundColor.value()); + + this->drawSelectionFrame(x, y, byteAddress, 1, cellStartPos, cellSize); + } + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + this->m_characterCellPadding / 2); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushItemWidth(CharacterSize.x); + if (!isCurrRegionValid(byteAddress)) + ImGui::TextFormatted("?"); + else + this->drawCell(byteAddress, &bytes[x], 1, cellHovered, CellType::ASCII); + ImGui::PopItemWidth(); + ImGui::PopStyleVar(); + } + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + // Draw Custom encoding column + if (this->m_currCustomEncoding.has_value()) { + std::vector> encodingData; + u32 offset = 0; + do { + const u64 address = y * this->m_bytesPerRow + offset + provider->getBaseAddress() + provider->getCurrentPageAddress(); + + auto result = queryCustomEncodingData(*this->m_currCustomEncoding, address); + offset += std::max(1, result.advance); + + encodingData.emplace_back(address, result); + } while (offset < this->m_bytesPerRow); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); + ImGui::PushID(y); + if (ImGui::BeginTable("##encoding_cell", encodingData.size(), ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible)) { + ImGui::TableNextRow(); + + for (const auto &[address, data] : encodingData) { + ImGui::TableNextColumn(); + + const auto cellStartPos = getCellPosition(); + const auto cellSize = ImGui::CalcTextSize(data.displayValue.c_str()) * ImVec2(1, 0) + ImVec2(0, CharacterSize.y); + const bool cellHovered = ImGui::IsMouseHoveringRect(cellStartPos, cellStartPos + cellSize, true); + + + const auto x = address % this->m_bytesPerRow; + if (x < validBytes && isCurrRegionValid(address)) { + auto [foregroundColor, backgroundColor] = cellColors[x / bytesPerCell]; + + backgroundColor = applySelectionColor(address, backgroundColor); + + // Draw highlights and selection + if (backgroundColor.has_value()) { + auto drawList = ImGui::GetWindowDrawList(); + + // Draw background color + drawList->AddRectFilled(cellStartPos, cellStartPos + cellSize, backgroundColor.value()); + + this->drawSelectionFrame(x, y, address, 1, cellStartPos, cellSize); + } + + ImGui::PushItemWidth(cellSize.x); + ImGui::TextFormattedColored(data.color, "{}", data.displayValue); + ImGui::PopItemWidth(); + + this->handleSelection(address, data.advance, &bytes[address % this->m_bytesPerRow], cellHovered); + } + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + ImGui::PopID(); + + } + + // Scroll to the cursor if it's either at the top or bottom edge of the screen + if (this->m_shouldScrollToSelection && isSelectionValid()) { + // Make sure simply clicking on a byte at the edge of the screen won't cause scrolling + if ((ImGui::IsMouseDown(ImGuiMouseButton_Left) && *this->m_selectionStart != *this->m_selectionEnd)) { + auto fractionPerLine = 1.0 / (this->m_visibleRowCount + 1); + + if (y == (u64(clipper.DisplayStart) + 3)) { + if (i128((*this->m_selectionEnd)->value() - provider->getBaseAddress() - provider->getCurrentPageAddress()) <= (i64(clipper.DisplayStart + 3) * this->m_bytesPerRow)) { + this->m_shouldScrollToSelection = false; + ImGui::SetScrollHereY(fractionPerLine * 5); + + } + } else if (y == (u64(clipper.DisplayEnd) - 1)) { + if (i128((*this->m_selectionEnd)->value() - provider->getBaseAddress() - provider->getCurrentPageAddress()) >= (i64(clipper.DisplayEnd - 2) * this->m_bytesPerRow)) { + this->m_shouldScrollToSelection = false; + ImGui::SetScrollHereY(fractionPerLine * (this->m_visibleRowCount)); + } + } + } + + // If the cursor is off-screen, directly jump to the byte + if (this->m_shouldJumpWhenOffScreen) { + this->m_shouldJumpWhenOffScreen = false; + + const auto pageAddress = provider->getCurrentPageAddress() + provider->getBaseAddress(); + auto newSelection = getSelection(); + newSelection.address -= pageAddress; + + if ((newSelection.getStartAddress()) < u64(clipper.DisplayStart * this->m_bytesPerRow)) + this->jumpToSelection(false); + if ((newSelection.getEndAddress()) > u64(clipper.DisplayEnd * this->m_bytesPerRow)) + this->jumpToSelection(false); + + } + } + } + } + + // Handle jumping to selection + if (this->m_shouldJumpToSelection) { + this->m_shouldJumpToSelection = false; + + auto newSelection = getSelection(); + provider->setCurrentPage(provider->getPageOfAddress(newSelection.address).value_or(0)); + + const auto pageAddress = provider->getCurrentPageAddress() + provider->getBaseAddress(); + auto scrollPos = (static_cast(newSelection.getStartAddress() - pageAddress) / this->m_bytesPerRow) * CharacterSize.y; + bool scrollUpwards = scrollPos < ImGui::GetScrollY(); + auto scrollFraction = scrollUpwards ? 0.0F : (1.0F - ((1.0F / this->m_visibleRowCount) * 2)); + + if (this->m_centerOnJump) { + scrollFraction = 0.5F; + this->m_centerOnJump = false; + } + + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scrollPos, scrollFraction); + } + + if (!this->m_syncScrolling) { + if (this->m_shouldUpdateScrollPosition) { + this->m_shouldUpdateScrollPosition = false; + ImGui::SetScrollY(**this->m_scrollPosition); + } else { + **this->m_scrollPosition = ImGui::GetScrollY(); + } + } + + + } else { + ImGui::TextFormattedCentered("hex.builtin.view.hex_editor.no_bytes"_lang); + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + this->m_enteredEditingMode = false; + } + + void HexEditor::drawFooter(const ImVec2 &size) { + if (ImHexApi::Provider::isValid()) { + auto provider = ImHexApi::Provider::get(); + const auto pageCount = provider->getPageCount(); + constexpr static u32 MinPage = 1; + + const auto windowEndPos = ImGui::GetWindowPos() + ImGui::GetWindowSize() - ImGui::GetStyle().WindowPadding; + ImGui::GetWindowDrawList()->AddLine(windowEndPos - ImVec2(0, size.y - 1_scaled), windowEndPos - size + ImVec2(0, 1_scaled), ImGui::GetColorU32(ImGuiCol_Separator), 2.0_scaled); + + if (ImGui::BeginChild("##footer", size, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::BeginTable("##footer_table", 2)) { + ImGui::TableNextRow(); + + // Page slider + ImGui::TableNextColumn(); + { + u32 page = provider->getCurrentPage() + 1; + + ImGui::TextFormatted("{}: ", "hex.builtin.view.hex_editor.page"_lang); + ImGui::SameLine(); + + ImGui::BeginDisabled(pageCount <= 1); + { + if (ImGui::SliderScalar("##page_selection", ImGuiDataType_U32, &page, &MinPage, &pageCount, hex::format("%d / {}", pageCount).c_str())) + provider->setCurrentPage(page - 1); + } + ImGui::EndDisabled(); + } + + // Page Address + ImGui::TableNextColumn(); + { + ImGui::TextFormatted("{0}: 0x{1:08X} - 0x{2:08X} ({1} - {2})", "hex.builtin.view.hex_editor.region"_lang, provider->getCurrentPageAddress(), provider->getSize()); + } + + ImGui::TableNextRow(); + + // Selection + ImGui::TableNextColumn(); + { + auto selection = getSelection(); + std::string value; + if (isSelectionValid()) { + value = hex::format("0x{0:08X} - 0x{1:08X} (0x{2:X} | {3})", + selection.getStartAddress(), + selection.getEndAddress(), + selection.getSize(), + hex::toByteString(selection.getSize()) + ); + } + else + value = std::string("hex.builtin.view.hex_editor.selection.none"_lang); + + ImGui::TextFormatted("{0}: {1}", "hex.builtin.view.hex_editor.selection"_lang, value); + } + + // Loaded data size + ImGui::TableNextColumn(); + { + ImGui::TextFormatted("{0}: 0x{1:08X} (0x{2:X} | {3})", "hex.builtin.view.hex_editor.data_size"_lang, + provider->getActualSize(), + provider->getActualSize(), + hex::toByteString(provider->getActualSize()) + ); + } + + ImGui::EndTable(); + } + + } + ImGui::EndChild(); + } + } + + void HexEditor::handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered) { + if (ImGui::IsWindowHovered() && cellHovered) { + drawTooltip(address, data, bytesPerCell); + + auto endAddress = address + bytesPerCell - 1; + auto &selectionStart = **this->m_selectionStart; + + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + this->setSelection(selectionStart.value_or(address), endAddress); + this->scrollToSelection(); + } + else if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + if (ImGui::GetIO().KeyShift) + this->setSelection(selectionStart.value_or(address), endAddress); + else + this->setSelection(address, endAddress); + + this->scrollToSelection(); + } + } + } + + void HexEditor::draw() { + const auto FooterSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 2.3); + const auto TableSize = ImGui::GetContentRegionAvail() - ImVec2(0, FooterSize.y); + + this->drawEditor(TableSize); + this->drawFooter(FooterSize); + + this->m_selectionChanged = false; + } + +} \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 36b2a08f1..26045c2d7 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -27,7 +27,7 @@ namespace hex::plugin::builtin { ImGui::EndTabItem(); } - ImGui::BeginDisabled(!ViewHexEditor::isSelectionValid()); + ImGui::BeginDisabled(!editor->isSelectionValid()); if (ImGui::BeginTabItem("hex.builtin.view.hex_editor.goto.offset.relative"_lang)) { this->m_mode = Mode::Relative; ImGui::EndTabItem(); @@ -57,21 +57,21 @@ namespace hex::plugin::builtin { switch (this->m_mode) { case Mode::Absolute: { - newAddress = inputResult; - } + newAddress = inputResult; + } break; case Mode::Relative: { - const auto selection = ViewHexEditor::getSelection(); - newAddress = selection.getStartAddress() + inputResult; - } + const auto selection = editor->getSelection(); + newAddress = selection.getStartAddress() + inputResult; + } break; case Mode::Begin: { - newAddress = provider->getBaseAddress() + provider->getCurrentPageAddress() + inputResult; - } + newAddress = provider->getBaseAddress() + provider->getCurrentPageAddress() + inputResult; + } break; case Mode::End: { - newAddress = provider->getActualSize() - inputResult; - } + newAddress = provider->getActualSize() - inputResult; + } break; } @@ -183,10 +183,10 @@ namespace hex::plugin::builtin { if (ImGui::BeginTabItem("hex.builtin.view.hex_editor.search.string"_lang)) { if (ImGui::InputTextIcon("##input", ICON_VS_SYMBOL_KEY, this->m_input, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { - if (!this->m_input.empty()) { + if (!this->m_input.empty()) { this->m_shouldSearch = true; this->m_backwards = false; - } + } } this->drawButtons(); @@ -211,7 +211,7 @@ namespace hex::plugin::builtin { auto region = this->findSequence(searchSequence, this->m_backwards); if (region.has_value()) { - if (ViewHexEditor::getSelection() == region) { + if (editor->getSelection() == region) { if (this->m_nextSearchPosition.has_value()) this->m_searchPosition = this->m_nextSearchPosition.value(); this->m_nextSearchPosition.reset(); @@ -460,321 +460,26 @@ namespace hex::plugin::builtin { u64 m_size; }; - - /* Data Visualizer */ - - class DataVisualizerAscii : public hex::ContentRegistry::HexEditor::DataVisualizer { - public: - DataVisualizerAscii() : DataVisualizer(1, 1) { } - - void draw(u64 address, const u8 *data, size_t size, bool upperCase) override { - hex::unused(address, upperCase); - - if (size == 1) { - const u8 c = data[0]; - if (std::isprint(c)) - ImGui::Text("%c", c); - else - ImGui::TextDisabled("."); - } - else - ImGui::TextDisabled("."); - } - - bool drawEditing(u64 address, u8 *data, size_t size, bool upperCase, bool startedEditing) override { - hex::unused(address, startedEditing, upperCase); - - if (size == 1) { - struct UserData { - u8 *data; - i32 maxChars; - - bool editingDone; - }; - - UserData userData = { - .data = data, - .maxChars = this->getMaxCharsPerCell(), - - .editingDone = false - }; - - ImGui::PushID(reinterpret_cast(address)); - char buffer[2] = { std::isprint(data[0]) ? char(data[0]) : '.', 0x00 }; - ImGui::InputText("##editing_input", buffer, 2, TextInputFlags | ImGuiInputTextFlags_CallbackEdit, [](ImGuiInputTextCallbackData *data) -> int { - auto &userData = *reinterpret_cast(data->UserData); - - if (data->BufTextLen >= userData.maxChars) { - userData.editingDone = true; - userData.data[0] = data->Buf[0]; - } - - return 0; - }, &userData); - ImGui::PopID(); - - return userData.editingDone || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Escape); - } - else - return false; - } - }; - /* Hex Editor */ ViewHexEditor::ViewHexEditor() : View("hex.builtin.view.hex_editor.name") { - this->m_currDataVisualizer = ContentRegistry::HexEditor::impl::getVisualizers()["hex.builtin.visualizer.hexadecimal.8bit"]; + this->m_hexEditor = std::make_unique(&this->m_selectionStart, &this->m_selectionEnd, &this->m_scrollPosition); + + EventManager::subscribe(this, [this](auto *, auto *newProvider) { + auto &data = ProviderExtraData::get(newProvider).editor; + + this->m_selectionStart = &data.selectionStart; + this->m_selectionEnd = &data.selectionEnd; + this->m_scrollPosition = &data.scrollPosition; + }); this->registerShortcuts(); this->registerEvents(); this->registerMenuItems(); - - ImHexApi::HexEditor::addForegroundHighlightingProvider([this](u64 address, const u8 *data, size_t size, bool hasColor) -> std::optional { - hex::unused(address); - - if (hasColor) - return std::nullopt; - - if (!this->m_grayOutZero) - return std::nullopt; - - for (u32 i = 0; i < size; i++) - if (data[i] != 0x00) - return std::nullopt; - - return ImGui::GetColorU32(ImGuiCol_TextDisabled); - }); - } - constexpr static u16 getByteColumnSeparatorCount(u16 columnCount) { - return (columnCount - 1) / 8; - } - - constexpr static bool isColumnSeparatorColumn(u16 currColumn, u16 columnCount) { - return currColumn > 0 && (currColumn) < columnCount && ((currColumn) % 8) == 0; - } - - static std::optional queryBackgroundColor(u64 address, const u8 *data, size_t size) { - std::optional result; - for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getBackgroundHighlightingFunctions()) { - if (auto color = callback(address, data, size, result.has_value()); color.has_value()) - return color.value(); - } - - if (result.has_value()) - return result; - - for (const auto &[id, highlighting] : ImHexApi::HexEditor::impl::getBackgroundHighlights()) { - if (highlighting.getRegion().overlaps({ address, size })) - return highlighting.getColor(); - } - - return std::nullopt; - } - - static std::optional queryForegroundColor(u64 address, const u8 *data, size_t size) { - std::optional result; - for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getForegroundHighlightingFunctions()) { - if (auto color = callback(address, data, size, result.has_value()); color.has_value()) - result = color; - } - - if (result.has_value()) - return result; - - for (const auto &[id, highlighting] : ImHexApi::HexEditor::impl::getForegroundHighlights()) { - if (highlighting.getRegion().overlaps({ address, size })) - return highlighting.getColor(); - } - - return std::nullopt; - } - - std::optional ViewHexEditor::applySelectionColor(u64 byteAddress, std::optional color) { - if (isSelectionValid()) { - auto selection = getSelection(); - - if (byteAddress >= selection.getStartAddress() && byteAddress <= selection.getEndAddress()) { - if (color.has_value()) - color = (ImAlphaBlendColors(color.value(), this->m_selectionColor)) & 0x00FFFFFF; - else - color = this->m_selectionColor; - } - } - - if (color.has_value()) - color = (*color & 0x00FFFFFF) | (this->m_selectionColor & 0xFF000000); - - return color; - } - - struct CustomEncodingData { - std::string displayValue; - size_t advance; - ImColor color; - }; - - static CustomEncodingData queryCustomEncodingData(const EncodingFile &encodingFile, u64 address) { - const auto longestSequence = encodingFile.getLongestSequence(); - - if (longestSequence == 0) - return { ".", 1, 0xFFFF8000 }; - - auto provider = ImHexApi::Provider::get(); - size_t size = std::min(longestSequence, provider->getActualSize() - address); - - std::vector buffer(size); - provider->read(address + provider->getBaseAddress() + provider->getCurrentPageAddress(), buffer.data(), size); - - const auto [decoded, advance] = encodingFile.getEncodingFor(buffer); - const ImColor color = [&decoded = decoded, &advance = advance]{ - if (decoded.length() == 1 && std::isalnum(decoded[0])) - return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarBlue); - else if (decoded.length() == 1 && advance == 1) - return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarRed); - else if (decoded.length() > 1 && advance == 1) - return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow); - else if (advance > 1) - return ImGui::GetColorU32(ImGuiCol_Text); - else - return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarBlue); - }(); - - return { std::string(decoded), advance, color }; - } - - static auto getCellPosition() { - return ImGui::GetCursorScreenPos() - ImGui::GetStyle().CellPadding; - } - - static void drawTooltip(u64 address, const u8 *data, size_t size) { - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, scaled(ImVec2(5, 5))); - - for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getTooltipFunctions()) { - callback(address, data, size); - } - - for (const auto &[id, tooltip] : ImHexApi::HexEditor::impl::getTooltips()) { - if (tooltip.getRegion().overlaps({ address, size })) { - ImGui::BeginTooltip(); - if (ImGui::BeginTable("##tooltips", 1, ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - ImGui::ColorButton(tooltip.getValue().c_str(), ImColor(tooltip.getColor())); - ImGui::SameLine(0, 10); - ImGui::TextUnformatted(tooltip.getValue().c_str()); - - ImGui::PushStyleColor(ImGuiCol_TableRowBg, tooltip.getColor()); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, tooltip.getColor()); - ImGui::EndTable(); - ImGui::PopStyleColor(2); - } - ImGui::EndTooltip(); - } - } - - ImGui::PopStyleVar(); - } - - void ViewHexEditor::drawCell(u64 address, u8 *data, size_t size, bool hovered, CellType cellType) { - static DataVisualizerAscii asciiVisualizer; - - auto provider = ImHexApi::Provider::get(); - - if (this->m_shouldUpdateEditingValue) { - this->m_shouldUpdateEditingValue = false; - - this->m_editingBytes.resize(size); - std::memcpy(this->m_editingBytes.data(), data, size); - } - - if (this->m_editingAddress != address || this->m_editingCellType != cellType) { - if (cellType == CellType::Hex) - this->m_currDataVisualizer->draw(address, data, size, this->m_upperCaseHex); - else - asciiVisualizer.draw(address, data, size, this->m_upperCaseHex); - - if (hovered && provider->isWritable()) { - // Enter editing mode when double-clicking a cell - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - this->m_editingAddress = address; - this->m_shouldModifyValue = false; - this->m_enteredEditingMode = true; - - this->m_editingBytes.resize(size); - std::memcpy(this->m_editingBytes.data(), data, size); - this->m_editingCellType = cellType; - } - } - } - else { - ImGui::SetKeyboardFocusHere(); - ImGui::SetNextFrameWantCaptureKeyboard(true); - - bool shouldExitEditingMode = true; - if (cellType == this->m_editingCellType && cellType == CellType::Hex) - shouldExitEditingMode = this->m_currDataVisualizer->drawEditing(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size(), this->m_upperCaseHex, this->m_enteredEditingMode); - else if (cellType == this->m_editingCellType && cellType == CellType::ASCII) - shouldExitEditingMode = asciiVisualizer.drawEditing(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size(), this->m_upperCaseHex, this->m_enteredEditingMode); - - if (shouldExitEditingMode || this->m_shouldModifyValue) { - - provider->write(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size()); - - if (!this->m_selectionChanged && !ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - auto nextEditingAddress = *this->m_editingAddress + this->m_currDataVisualizer->getBytesPerCell(); - this->setSelection(nextEditingAddress, nextEditingAddress); - - if (nextEditingAddress >= provider->getSize()) - this->m_editingAddress = std::nullopt; - else - this->m_editingAddress = nextEditingAddress; - } else { - this->m_editingAddress = std::nullopt; - } - - this->m_shouldModifyValue = false; - this->m_shouldUpdateEditingValue = true; - } - - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !hovered && !this->m_enteredEditingMode) { - this->m_editingAddress = std::nullopt; - this->m_shouldModifyValue = false; - } - - if (!this->m_editingAddress.has_value()) - this->m_editingCellType = CellType::None; - } - } - - void ViewHexEditor::drawSelectionFrame(u32 x, u32 y, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize) const { - if (!isSelectionValid()) return; - - const auto selection = getSelection(); - if (!Region { byteAddress, 1 }.isWithin(selection)) - return; - - const color_t SelectionFrameColor = ImGui::GetColorU32(ImGuiCol_Text); - - auto drawList = ImGui::GetWindowDrawList(); - - // Draw vertical line at the left of first byte and the start of the line - if (x == 0 || byteAddress == selection.getStartAddress()) - drawList->AddLine(cellPos, cellPos + ImVec2(0, cellSize.y), ImColor(SelectionFrameColor), 1.0F); - - // Draw vertical line at the right of the last byte and the end of the line - if (x == u16((this->m_bytesPerRow / bytesPerCell) - 1) || (byteAddress + bytesPerCell) > selection.getEndAddress()) - drawList->AddLine(cellPos + ImVec2(cellSize.x, -1), cellPos + cellSize, ImColor(SelectionFrameColor), 1.0F); - - // Draw horizontal line at the top of the bytes - if (y == 0 || (byteAddress - this->m_bytesPerRow) < selection.getStartAddress()) - drawList->AddLine(cellPos, cellPos + ImVec2(cellSize.x + 1, 0), ImColor(SelectionFrameColor), 1.0F); - - // Draw horizontal line at the bottom of the bytes - if ((byteAddress + this->m_bytesPerRow) > selection.getEndAddress()) - drawList->AddLine(cellPos + ImVec2(0, cellSize.y), cellPos + cellSize + ImVec2(1, 0), ImColor(SelectionFrameColor), 1.0F); + ViewHexEditor::~ViewHexEditor() { + EventManager::unsubscribe(this); } void ViewHexEditor::drawPopup() { @@ -786,7 +491,7 @@ namespace hex::plugin::builtin { ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin() - ImGui::GetStyle().WindowPadding, ImGuiCond_Appearing); if (ImGui::BeginPopup("##hex_editor_popup", ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |ImGuiWindowFlags_NoTitleBar)) { - + // Force close the popup when user is editing an input if(ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape))){ ImGui::CloseCurrentPopup(); @@ -824,476 +529,13 @@ namespace hex::plugin::builtin { } } - void ViewHexEditor::drawEditor(const ImVec2 &size) { - const float SeparatorColumWidth = 6_scaled; - const auto CharacterSize = ImGui::CalcTextSize("0"); - - const auto bytesPerCell = this->m_currDataVisualizer->getBytesPerCell(); - const u16 columnCount = this->m_bytesPerRow / bytesPerCell; - const auto byteColumnCount = columnCount + getByteColumnSeparatorCount(columnCount); - - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.5, 0)); - if (ImGui::BeginTable("##hex", 2 + byteColumnCount + 2 + 2 , ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible, size)) { - View::discardNavigationRequests(); - ImGui::TableSetupScrollFreeze(0, 2); - - // Row address column - ImGui::TableSetupColumn("hex.builtin.common.address"_lang); - ImGui::TableSetupColumn(""); - - // Byte columns - for (u16 i = 0; i < columnCount; i++) { - if (isColumnSeparatorColumn(i, columnCount)) - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, SeparatorColumWidth); - - ImGui::TableSetupColumn(hex::format(this->m_upperCaseHex ? "{:0{}X}" : "{:0{}x}", i * bytesPerCell, this->m_currDataVisualizer->getMaxCharsPerCell()).c_str(), ImGuiTableColumnFlags_WidthFixed, CharacterSize.x * this->m_currDataVisualizer->getMaxCharsPerCell() + 6 + this->m_byteCellPadding); - } - - // ASCII column - ImGui::TableSetupColumn(""); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, (CharacterSize.x + this->m_characterCellPadding) * this->m_bytesPerRow); - - // Custom encoding column - ImGui::TableSetupColumn(""); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - for (i32 i = 0; i < ImGui::TableGetColumnCount(); i++) { - ImGui::TableNextColumn(); - ImGui::TextUnformatted(ImGui::TableGetColumnName(i)); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + CharacterSize.y / 2); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - if (ImHexApi::Provider::isValid()) { - auto provider = ImHexApi::Provider::get(); - auto &providerData = ProviderExtraData::get(provider).editor; - - std::pair validRegion = { Region::Invalid(), false }; - const auto isCurrRegionValid = [&validRegion, &provider](u64 address){ - auto &[currRegion, currRegionValid] = validRegion; - if (!Region{ address, 1 }.isWithin(currRegion)) { - validRegion = provider->getRegionValidity(address); - } - - return currRegionValid; - }; - - ImGuiListClipper clipper; - - clipper.Begin(std::ceil(provider->getSize() / (long double)(this->m_bytesPerRow)), CharacterSize.y); - while (clipper.Step()) { - this->m_visibleRowCount = clipper.DisplayEnd - clipper.DisplayStart; - - // Loop over rows - for (u64 y = u64(clipper.DisplayStart); y < u64(clipper.DisplayEnd); y++) { - - // Draw address column - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextFormatted(this->m_upperCaseHex ? "{:08X}: " : "{:08x}: ", y * this->m_bytesPerRow + provider->getBaseAddress() + provider->getCurrentPageAddress()); - ImGui::TableNextColumn(); - - const u8 validBytes = std::min(this->m_bytesPerRow, provider->getSize() - y * this->m_bytesPerRow); - - std::vector bytes(this->m_bytesPerRow, 0x00); - provider->read(y * this->m_bytesPerRow + provider->getBaseAddress() + provider->getCurrentPageAddress(), bytes.data(), validBytes); - - std::vector, std::optional>> cellColors; - { - for (u64 x = 0; x < std::ceil(float(validBytes) / bytesPerCell); x++) { - const u64 byteAddress = y * this->m_bytesPerRow + x * bytesPerCell + provider->getBaseAddress() + provider->getCurrentPageAddress(); - - const auto cellBytes = std::min(validBytes, bytesPerCell); - - // Query cell colors - if (x < std::ceil(float(validBytes) / bytesPerCell)) { - const auto foregroundColor = queryForegroundColor(byteAddress, &bytes[x * cellBytes], cellBytes); - const auto backgroundColor = queryBackgroundColor(byteAddress, &bytes[x * cellBytes], cellBytes); - - cellColors.emplace_back( - foregroundColor, - backgroundColor - ); - } else { - cellColors.emplace_back( - std::nullopt, - std::nullopt - ); - } - } - } - - // Draw byte columns - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(3, 0)); - - for (u64 x = 0; x < columnCount; x++) { - const u64 byteAddress = y * this->m_bytesPerRow + x * bytesPerCell + provider->getBaseAddress() + provider->getCurrentPageAddress(); - - ImGui::TableNextColumn(); - if (isColumnSeparatorColumn(x, columnCount)) - ImGui::TableNextColumn(); - - if (x < std::ceil(float(validBytes) / bytesPerCell)) { - auto cellStartPos = getCellPosition(); - auto cellSize = (CharacterSize * ImVec2(this->m_currDataVisualizer->getMaxCharsPerCell(), 1) + (ImVec2(3, 2) * ImGui::GetStyle().CellPadding) - ImVec2(1, 0) * ImGui::GetStyle().CellPadding) + ImVec2(1 + this->m_byteCellPadding, 0); - auto maxCharsPerCell = this->m_currDataVisualizer->getMaxCharsPerCell(); - - auto [foregroundColor, backgroundColor] = cellColors[x]; - - if (isColumnSeparatorColumn(x + 1, columnCount) && cellColors.size() > x + 1) { - auto separatorAddress = x + y * columnCount; - auto [nextForegroundColor, nextBackgroundColor] = cellColors[x + 1]; - if ((isSelectionValid() && getSelection().overlaps({ separatorAddress, 1 }) && getSelection().getEndAddress() != separatorAddress) || backgroundColor == nextBackgroundColor) - cellSize.x += SeparatorColumWidth + 1; - } - - if (y == u64(clipper.DisplayStart)) - cellSize.y -= (ImGui::GetStyle().CellPadding.y + 1); - - backgroundColor = applySelectionColor(byteAddress, backgroundColor); - - // Draw highlights and selection - if (backgroundColor.has_value()) { - auto drawList = ImGui::GetWindowDrawList(); - - // Draw background color - drawList->AddRectFilled(cellStartPos, cellStartPos + cellSize, backgroundColor.value()); - - // Draw frame around mouse selection - this->drawSelectionFrame(x, y, byteAddress, bytesPerCell, cellStartPos, cellSize); - } - - const bool cellHovered = ImGui::IsMouseHoveringRect(cellStartPos, cellStartPos + cellSize, false); - - this->handleSelection(byteAddress, bytesPerCell, &bytes[x * bytesPerCell], cellHovered); - - // Get byte foreground color - if (foregroundColor.has_value()) - ImGui::PushStyleColor(ImGuiCol_Text, *foregroundColor); - - // Draw cell content - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::PushItemWidth((CharacterSize * maxCharsPerCell).x); - if (isCurrRegionValid(byteAddress)) - this->drawCell(byteAddress, &bytes[x * bytesPerCell], bytesPerCell, cellHovered, CellType::Hex); - else - ImGui::TextFormatted("{}", std::string(maxCharsPerCell, '?')); - ImGui::PopItemWidth(); - ImGui::PopStyleVar(); - - if (foregroundColor.has_value()) - ImGui::PopStyleColor(); - } - } - ImGui::PopStyleVar(); - - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - - // Draw ASCII column - if (this->m_showAscii) { - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); - if (ImGui::BeginTable("##ascii_column", this->m_bytesPerRow)) { - for (u64 x = 0; x < this->m_bytesPerRow; x++) - ImGui::TableSetupColumn(hex::format("##ascii_cell{}", x).c_str(), ImGuiTableColumnFlags_WidthFixed, CharacterSize.x + this->m_characterCellPadding); - - ImGui::TableNextRow(); - - for (u64 x = 0; x < this->m_bytesPerRow; x++) { - ImGui::TableNextColumn(); - - const u64 byteAddress = y * this->m_bytesPerRow + x + provider->getBaseAddress() + provider->getCurrentPageAddress(); - - const auto cellStartPos = getCellPosition(); - const auto cellSize = CharacterSize + ImVec2(this->m_characterCellPadding, 0); - - const bool cellHovered = ImGui::IsMouseHoveringRect(cellStartPos, cellStartPos + cellSize, true); - - if (x < validBytes) { - this->handleSelection(byteAddress, bytesPerCell, &bytes[x], cellHovered); - - auto [foregroundColor, backgroundColor] = cellColors[x / bytesPerCell]; - - backgroundColor = applySelectionColor(byteAddress, backgroundColor); - - // Draw highlights and selection - if (backgroundColor.has_value()) { - auto drawList = ImGui::GetWindowDrawList(); - - // Draw background color - drawList->AddRectFilled(cellStartPos, cellStartPos + cellSize, backgroundColor.value()); - - this->drawSelectionFrame(x, y, byteAddress, 1, cellStartPos, cellSize); - } - - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + this->m_characterCellPadding / 2); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::PushItemWidth(CharacterSize.x); - if (!isCurrRegionValid(byteAddress)) - ImGui::TextFormatted("?"); - else - this->drawCell(byteAddress, &bytes[x], 1, cellHovered, CellType::ASCII); - ImGui::PopItemWidth(); - ImGui::PopStyleVar(); - } - } - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - } - - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - - // Draw Custom encoding column - if (this->m_currCustomEncoding.has_value()) { - std::vector> encodingData; - u32 offset = 0; - do { - const u64 address = y * this->m_bytesPerRow + offset + provider->getBaseAddress() + provider->getCurrentPageAddress(); - - auto result = queryCustomEncodingData(*this->m_currCustomEncoding, address); - offset += std::max(1, result.advance); - - encodingData.emplace_back(address, result); - } while (offset < this->m_bytesPerRow); - - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); - ImGui::PushID(y); - if (ImGui::BeginTable("##encoding_cell", encodingData.size(), ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible)) { - ImGui::TableNextRow(); - - for (const auto &[address, data] : encodingData) { - ImGui::TableNextColumn(); - - const auto cellStartPos = getCellPosition(); - const auto cellSize = ImGui::CalcTextSize(data.displayValue.c_str()) * ImVec2(1, 0) + ImVec2(0, CharacterSize.y); - const bool cellHovered = ImGui::IsMouseHoveringRect(cellStartPos, cellStartPos + cellSize, true); - - - const auto x = address % this->m_bytesPerRow; - if (x < validBytes && isCurrRegionValid(address)) { - auto [foregroundColor, backgroundColor] = cellColors[x / bytesPerCell]; - - backgroundColor = applySelectionColor(address, backgroundColor); - - // Draw highlights and selection - if (backgroundColor.has_value()) { - auto drawList = ImGui::GetWindowDrawList(); - - // Draw background color - drawList->AddRectFilled(cellStartPos, cellStartPos + cellSize, backgroundColor.value()); - - this->drawSelectionFrame(x, y, address, 1, cellStartPos, cellSize); - } - - ImGui::PushItemWidth(cellSize.x); - ImGui::TextFormattedColored(data.color, "{}", data.displayValue); - ImGui::PopItemWidth(); - - this->handleSelection(address, data.advance, &bytes[address % this->m_bytesPerRow], cellHovered); - } - } - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - ImGui::PopID(); - - } - - // Scroll to the cursor if it's either at the top or bottom edge of the screen - if (this->m_shouldScrollToSelection && isSelectionValid()) { - // Make sure simply clicking on a byte at the edge of the screen won't cause scrolling - if ((ImGui::IsMouseDown(ImGuiMouseButton_Left) && providerData.selectionStart != providerData.selectionEnd)) { - auto fractionPerLine = 1.0 / (this->m_visibleRowCount + 1); - - if (y == (u64(clipper.DisplayStart) + 3)) { - if (i128(*providerData.selectionEnd - provider->getBaseAddress() - provider->getCurrentPageAddress()) <= (i64(clipper.DisplayStart + 3) * this->m_bytesPerRow)) { - this->m_shouldScrollToSelection = false; - ImGui::SetScrollHereY(fractionPerLine * 5); - - } - } else if (y == (u64(clipper.DisplayEnd) - 1)) { - if (i128(*providerData.selectionEnd - provider->getBaseAddress() - provider->getCurrentPageAddress()) >= (i64(clipper.DisplayEnd - 2) * this->m_bytesPerRow)) { - this->m_shouldScrollToSelection = false; - ImGui::SetScrollHereY(fractionPerLine * (this->m_visibleRowCount)); - } - } - } - - // If the cursor is off-screen, directly jump to the byte - if (this->m_shouldJumpWhenOffScreen) { - this->m_shouldJumpWhenOffScreen = false; - - const auto pageAddress = provider->getCurrentPageAddress() + provider->getBaseAddress(); - auto newSelection = getSelection(); - newSelection.address -= pageAddress; - - if ((newSelection.getStartAddress()) < u64(clipper.DisplayStart * this->m_bytesPerRow)) - this->jumpToSelection(false); - if ((newSelection.getEndAddress()) > u64(clipper.DisplayEnd * this->m_bytesPerRow)) - this->jumpToSelection(false); - - } - } - } - } - - // Handle jumping to selection - if (this->m_shouldJumpToSelection) { - this->m_shouldJumpToSelection = false; - - auto newSelection = getSelection(); - provider->setCurrentPage(provider->getPageOfAddress(newSelection.address).value_or(0)); - - const auto pageAddress = provider->getCurrentPageAddress() + provider->getBaseAddress(); - auto scrollPos = (static_cast(newSelection.getStartAddress() - pageAddress) / this->m_bytesPerRow) * CharacterSize.y; - bool scrollUpwards = scrollPos < ImGui::GetScrollY(); - auto scrollFraction = scrollUpwards ? 0.0F : (1.0F - ((1.0F / this->m_visibleRowCount) * 2)); - - if (this->m_centerOnJump) { - scrollFraction = 0.5F; - this->m_centerOnJump = false; - } - - ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scrollPos, scrollFraction); - } - - if (!this->m_syncScrolling) { - if (this->m_shouldUpdateScrollPosition) { - this->m_shouldUpdateScrollPosition = false; - ImGui::SetScrollY(providerData.scrollPosition); - } else { - providerData.scrollPosition = ImGui::GetScrollY(); - } - } - - - } else { - ImGui::TextFormattedCentered("hex.builtin.view.hex_editor.no_bytes"_lang); - } - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - - this->m_enteredEditingMode = false; - } - - void ViewHexEditor::drawFooter(const ImVec2 &size) { - if (ImHexApi::Provider::isValid()) { - auto provider = ImHexApi::Provider::get(); - const auto pageCount = provider->getPageCount(); - constexpr static u32 MinPage = 1; - - const auto windowEndPos = ImGui::GetWindowPos() + ImGui::GetWindowSize() - ImGui::GetStyle().WindowPadding; - ImGui::GetWindowDrawList()->AddLine(windowEndPos - ImVec2(0, size.y - 1_scaled), windowEndPos - size + ImVec2(0, 1_scaled), ImGui::GetColorU32(ImGuiCol_Separator), 2.0_scaled); - - if (ImGui::BeginChild("##footer", size, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::BeginTable("##footer_table", 2)) { - ImGui::TableNextRow(); - - // Page slider - ImGui::TableNextColumn(); - { - u32 page = provider->getCurrentPage() + 1; - - ImGui::TextFormatted("{}: ", "hex.builtin.view.hex_editor.page"_lang); - ImGui::SameLine(); - - ImGui::BeginDisabled(pageCount <= 1); - { - if (ImGui::SliderScalar("##page_selection", ImGuiDataType_U32, &page, &MinPage, &pageCount, hex::format("%d / {}", pageCount).c_str())) - provider->setCurrentPage(page - 1); - } - ImGui::EndDisabled(); - } - - // Page Address - ImGui::TableNextColumn(); - { - ImGui::TextFormatted("{0}: 0x{1:08X} - 0x{2:08X} ({1} - {2})", "hex.builtin.view.hex_editor.region"_lang, provider->getCurrentPageAddress(), provider->getSize()); - } - - ImGui::TableNextRow(); - - // Selection - ImGui::TableNextColumn(); - { - auto selection = getSelection(); - std::string value; - if (isSelectionValid()) { - value = hex::format("0x{0:08X} - 0x{1:08X} (0x{2:X} | {3})", - selection.getStartAddress(), - selection.getEndAddress(), - selection.getSize(), - hex::toByteString(selection.getSize()) - ); - } - else - value = std::string("hex.builtin.view.hex_editor.selection.none"_lang); - - ImGui::TextFormatted("{0}: {1}", "hex.builtin.view.hex_editor.selection"_lang, value); - } - - // Loaded data size - ImGui::TableNextColumn(); - { - ImGui::TextFormatted("{0}: 0x{1:08X} (0x{2:X} | {3})", "hex.builtin.view.hex_editor.data_size"_lang, - provider->getActualSize(), - provider->getActualSize(), - hex::toByteString(provider->getActualSize()) - ); - } - - ImGui::EndTable(); - } - - } - ImGui::EndChild(); - } - } - - void ViewHexEditor::handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered) { - if (ImGui::IsWindowHovered() && cellHovered) { - drawTooltip(address, data, bytesPerCell); - - auto endAddress = address + bytesPerCell - 1; - auto &selectionStart = ProviderExtraData::getCurrent().editor.selectionStart; - - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { - this->setSelection(selectionStart.value_or(address), endAddress); - this->scrollToSelection(); - } - else if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { - if (ImGui::GetIO().KeyShift) - this->setSelection(selectionStart.value_or(address), endAddress); - else - this->setSelection(address, endAddress); - - this->scrollToSelection(); - } - } - } - void ViewHexEditor::drawContent() { if (ImGui::Begin(View::toWindowName(this->getUnlocalizedName()).c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { - const auto FooterSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 2.3); - const auto TableSize = ImGui::GetContentRegionAvail() - ImVec2(0, FooterSize.y); - + this->m_hexEditor->draw(); this->drawPopup(); - this->drawEditor(TableSize); - this->drawFooter(FooterSize); } ImGui::End(); - - this->m_selectionChanged = false; } static void save() { @@ -1391,32 +633,32 @@ namespace hex::plugin::builtin { }); // Remove selection - ShortcutManager::addShortcut(this, Keys::Escape, [] { + ShortcutManager::addShortcut(this, Keys::Escape, [this] { auto &data = ProviderExtraData::getCurrent().editor; data.selectionStart.reset(); data.selectionEnd.reset(); - EventManager::post(getSelection()); + EventManager::post(this->getSelection()); }); // Move cursor around ShortcutManager::addShortcut(this, Keys::Up, [this] { auto selection = getSelection(); - if (selection.getEndAddress() >= this->m_bytesPerRow) { - auto pos = selection.getEndAddress() - this->m_bytesPerRow; + if (selection.getEndAddress() >= this->m_hexEditor->getBytesPerRow()) { + auto pos = selection.getEndAddress() - this->m_hexEditor->getBytesPerRow(); this->setSelection(pos, pos); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); } }); ShortcutManager::addShortcut(this, Keys::Down, [this] { auto selection = getSelection(); - auto pos = selection.getEndAddress() + this->m_bytesPerRow; + auto pos = selection.getEndAddress() + this->m_hexEditor->getBytesPerRow(); this->setSelection(pos, pos); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, Keys::Left, [this] { auto selection = getSelection(); @@ -1424,8 +666,8 @@ namespace hex::plugin::builtin { if (selection.getEndAddress() > 0) { auto pos = selection.getEndAddress() - 1; this->setSelection(pos, pos); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); } }); ShortcutManager::addShortcut(this, Keys::Right, [this] { @@ -1433,78 +675,78 @@ namespace hex::plugin::builtin { auto pos = selection.getEndAddress() + 1; this->setSelection(pos, pos); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, Keys::PageUp, [this] { auto selection = getSelection(); - u64 visibleByteCount = this->m_bytesPerRow * this->m_visibleRowCount; + u64 visibleByteCount = this->m_hexEditor->getBytesPerRow() * this->m_hexEditor->getVisibleRowCount(); if (selection.getEndAddress() >= visibleByteCount) { auto pos = selection.getEndAddress() - visibleByteCount; this->setSelection(pos, pos); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); } }); ShortcutManager::addShortcut(this, Keys::PageDown, [this] { auto selection = getSelection(); - auto pos = selection.getEndAddress() + (this->m_bytesPerRow * this->m_visibleRowCount); + auto pos = selection.getEndAddress() + (this->m_hexEditor->getBytesPerRow() * this->m_hexEditor->getVisibleRowCount()); this->setSelection(pos, pos); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); // Move selection around ShortcutManager::addShortcut(this, SHIFT + Keys::Up, [this] { auto selection = getSelection(); - this->setSelection(std::max(selection.getStartAddress(), this->m_bytesPerRow) - this->m_bytesPerRow, selection.getEndAddress()); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->setSelection(std::max(selection.getStartAddress(), this->m_hexEditor->getBytesPerRow()) - this->m_hexEditor->getBytesPerRow(), selection.getEndAddress()); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, SHIFT + Keys::Down, [this] { auto selection = getSelection(); - this->setSelection(selection.getStartAddress() + this->m_bytesPerRow, selection.getEndAddress()); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->setSelection(selection.getStartAddress() + this->m_hexEditor->getBytesPerRow(), selection.getEndAddress()); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, SHIFT + Keys::Left, [this] { auto selection = getSelection(); this->setSelection(std::max(selection.getStartAddress(), 1) - 1, selection.getEndAddress()); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, SHIFT + Keys::Right, [this] { auto selection = getSelection(); this->setSelection(selection.getStartAddress() + 1, selection.getEndAddress()); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, Keys::PageUp, [this] { auto selection = getSelection(); - u64 visibleByteCount = this->m_bytesPerRow * this->m_visibleRowCount; + u64 visibleByteCount = this->m_hexEditor->getBytesPerRow() * this->m_hexEditor->getVisibleRowCount(); if (selection.getEndAddress() >= visibleByteCount) { auto pos = selection.getEndAddress() - visibleByteCount; this->setSelection(pos, selection.getEndAddress()); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); } }); ShortcutManager::addShortcut(this, Keys::PageDown, [this] { auto selection = getSelection(); - auto pos = selection.getEndAddress() + (this->m_bytesPerRow * this->m_visibleRowCount); + auto pos = selection.getEndAddress() + (this->m_hexEditor->getBytesPerRow() * this->m_hexEditor->getVisibleRowCount()); this->setSelection(pos, selection.getEndAddress()); - this->scrollToSelection(); - this->jumpIfOffScreen(); + this->m_hexEditor->scrollToSelection(); + this->m_hexEditor->jumpIfOffScreen(); }); ShortcutManager::addShortcut(this, CTRL + Keys::G, [this] { @@ -1519,23 +761,23 @@ namespace hex::plugin::builtin { }); // Copy - ShortcutManager::addShortcut(this, CTRL + Keys::C, [] { + ShortcutManager::addShortcut(this, CTRL + Keys::C, [this] { const auto selection = getSelection(); copyBytes(selection); }); - ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::C, [] { + ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::C, [this] { const auto selection = getSelection(); copyString(selection); }); // Paste - ShortcutManager::addShortcut(this, CTRL + Keys::V, [] { + ShortcutManager::addShortcut(this, CTRL + Keys::V, [this] { const auto selection = getSelection(); pasteBytes(selection, true); }); // Paste and resize - ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::V, [] { + ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::V, [this] { const auto selection = getSelection(); pasteBytes(selection, false); }); @@ -1556,10 +798,6 @@ namespace hex::plugin::builtin { } void ViewHexEditor::registerEvents() { - EventManager::subscribe(this, [this](auto) { - this->m_shouldModifyValue = true; - }); - EventManager::subscribe(this, [this](Region region) { auto provider = ImHexApi::Provider::get(); @@ -1583,86 +821,17 @@ namespace hex::plugin::builtin { } }); - EventManager::subscribe(this, [](auto ®ion) { + EventManager::subscribe(this, [this](auto ®ion) { if (isSelectionValid()) region = getSelection(); }); EventManager::subscribe(this, [this](auto, auto) { - this->m_shouldUpdateScrollPosition = true; + this->m_hexEditor->forceUpdateScrollPosition(); if (isSelectionValid()) EventManager::post(getSelection()); }); - - EventManager::subscribe(this, [this] { - { - auto bytesPerRow = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.bytes_per_row"); - - if (bytesPerRow.is_number()) - this->m_bytesPerRow = static_cast(bytesPerRow); - } - - { - auto ascii = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.ascii"); - - if (ascii.is_number()) - this->m_showAscii = static_cast(ascii); - } - - { - auto greyOutZeros = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.grey_zeros"); - - if (greyOutZeros.is_number()) - this->m_grayOutZero = static_cast(greyOutZeros); - } - - { - auto upperCaseHex = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.uppercase_hex"); - - if (upperCaseHex.is_number()) - this->m_upperCaseHex = static_cast(upperCaseHex); - } - - { - auto selectionColor = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.highlight_color"); - - if (selectionColor.is_number()) - this->m_selectionColor = static_cast(selectionColor); - } - - { - auto &visualizers = ContentRegistry::HexEditor::impl::getVisualizers(); - auto selectedVisualizer = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.visualizer"); - - if (selectedVisualizer.is_string() && visualizers.contains(selectedVisualizer)) - this->m_currDataVisualizer = visualizers[selectedVisualizer]; - else - this->m_currDataVisualizer = visualizers["hex.builtin.visualizer.hexadecimal.8bit"]; - } - - { - auto syncScrolling = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling"); - - if (syncScrolling.is_number()) - this->m_syncScrolling = static_cast(syncScrolling); - } - - { - auto padding = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding"); - - if (padding.is_number()) - this->m_byteCellPadding = static_cast(padding); - } - - { - auto padding = ContentRegistry::Settings::getSetting("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding"); - - if (padding.is_number()) - this->m_characterCellPadding = static_cast(padding); - } - - }); } void ViewHexEditor::registerMenuItems() { @@ -1698,7 +867,7 @@ namespace hex::plugin::builtin { View::showFileChooserPopup(paths, { {"Thingy Table File", "tbl"} }, [this](const auto &path) { - this->m_currCustomEncoding = EncodingFile(EncodingFile::Type::Thingy, path); + this->m_hexEditor->setCustomEncoding(EncodingFile(EncodingFile::Type::Thingy, path)); }); } });