#pragma once #include #include #include #include #include #include #include namespace hex::plugin::builtin::ui { class HexEditor { public: explicit HexEditor(prv::Provider *provider = nullptr); ~HexEditor(); void draw(float height = ImGui::GetContentRegionAvail().y); void setProvider(prv::Provider *provider) { this->m_provider = provider; this->m_currValidRegion = { Region::Invalid(), false }; } void setUnknownDataCharacter(char character) { this->m_unknownDataCharacter = character; } private: enum class CellType { None, Hex, ASCII }; void drawCell(u64 address, const 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 drawTooltip(u64 address, const u8 *data, size_t size) const; void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered); std::optional applySelectionColor(u64 byteAddress, std::optional color); public: void setSelectionUnchecked(std::optional start, std::optional end) { this->m_selectionStart = start; this->m_selectionEnd = end; this->m_cursorPosition = end; } void setSelection(const Region ®ion) { this->setSelection(region.getStartAddress(), region.getEndAddress()); } void setSelection(u128 start, u128 end) { if (!ImHexApi::Provider::isValid()) return; if (start > this->m_provider->getBaseAddress() + this->m_provider->getActualSize()) return; if (start < this->m_provider->getBaseAddress()) return; if (this->m_provider->getActualSize() == 0) return; const size_t maxAddress = this->m_provider->getActualSize() + this->m_provider->getBaseAddress() - 1; constexpr static auto alignDown = [](u128 value, u128 alignment) { return value & ~(alignment - 1); }; this->m_selectionChanged = this->m_selectionStart != start || this->m_selectionEnd != end; if (!this->m_selectionStart.has_value()) this->m_selectionStart = start; if (!this->m_selectionEnd.has_value()) this->m_selectionEnd = end; if (auto bytesPerCell = this->m_currDataVisualizer->getBytesPerCell(); bytesPerCell > 1) { if (end > start) { start = alignDown(start, bytesPerCell); end = alignDown(end, bytesPerCell) + (bytesPerCell - 1); } else { start = alignDown(start, bytesPerCell) + (bytesPerCell - 1); end = alignDown(end, bytesPerCell); } } this->m_selectionStart = std::clamp(start, 0, maxAddress); this->m_selectionEnd = std::clamp(end, 0, maxAddress); this->m_cursorPosition = this->m_selectionEnd; if (this->m_selectionChanged) { auto selection = this->getSelection(); EventManager::post(ImHexApi::HexEditor::ProviderRegion{ { selection.address, selection.size }, this->m_provider }); 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]] std::optional getCursorPosition() const { return this->m_cursorPosition; } void setCursorPosition(u64 cursorPosition) { this->m_cursorPosition = cursorPosition; } [[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; } [[nodiscard]] u16 getBytesPerCell() const { return this->m_currDataVisualizer->getBytesPerCell(); } 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 enableShowHumanReadableUnits(bool showHumanReadableUnits) { this->m_showHumanReadableUnits = showHumanReadableUnits; } void enableSyncScrolling(bool syncScrolling) { this->m_syncScrolling = syncScrolling; } void setByteCellPadding(u32 byteCellPadding) { this->m_byteCellPadding = byteCellPadding; } void setCharacterCellPadding(u32 characterCellPadding) { this->m_characterCellPadding = characterCellPadding; } [[nodiscard]] const std::optional& getCustomEncoding() const { return this->m_currCustomEncoding; } void setCustomEncoding(const EncodingFile &encoding) { this->m_currCustomEncoding = encoding; this->m_encodingLineStartAddresses.clear(); } void setCustomEncoding(EncodingFile &&encoding) { this->m_currCustomEncoding = std::move(encoding); this->m_encodingLineStartAddresses.clear(); } void forceUpdateScrollPosition() { this->m_shouldUpdateScrollPosition = true; } void setForegroundHighlightCallback(const std::function(u64, const u8 *, size_t)> &callback) { this->m_foregroundColorCallback = callback; } void setBackgroundHighlightCallback(const std::function(u64, const u8 *, size_t)> &callback) { this->m_backgroundColorCallback = callback; } void setTooltipCallback(const std::function &callback) { this->m_tooltipCallback = callback; } [[nodiscard]] float getScrollPosition() const { return this->m_scrollPosition; } void setScrollPosition(float scrollPosition) { this->m_scrollPosition = scrollPosition; } void setEditingAddress(u64 address) { this->m_editingAddress = address; this->m_shouldModifyValue = false; this->m_enteredEditingMode = true; this->m_editingBytes.resize(this->m_currDataVisualizer->getBytesPerCell()); this->m_provider->read(address + this->m_provider->getBaseAddress(), this->m_editingBytes.data(), this->m_editingBytes.size()); this->m_editingCellType = CellType::Hex; } void clearEditingAddress() { this->m_editingAddress = std::nullopt; } private: prv::Provider *m_provider; std::optional m_selectionStart; std::optional m_selectionEnd; std::optional m_cursorPosition; float m_scrollPosition = 0; u16 m_bytesPerRow = 16; std::endian m_dataVisualizerEndianness = std::endian::little; std::shared_ptr m_currDataVisualizer; char m_unknownDataCharacter = '?'; 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_showCustomEncoding = true; bool m_showHumanReadableUnits = true; bool m_syncScrolling = false; u32 m_byteCellPadding = 0, m_characterCellPadding = 0; std::optional m_currCustomEncoding; std::vector m_encodingLineStartAddresses; std::pair m_currValidRegion = { Region::Invalid(), false }; static std::optional defaultColorCallback(u64, const u8 *, size_t) { return std::nullopt; } static void defaultTooltipCallback(u64, const u8 *, size_t) { } std::function(u64, const u8 *, size_t)> m_foregroundColorCallback = defaultColorCallback, m_backgroundColorCallback = defaultColorCallback; std::function m_tooltipCallback = defaultTooltipCallback; }; }