2022-11-06 12:19:12 +01:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <hex.hpp>
|
|
|
|
#include <hex/api/imhex_api.hpp>
|
|
|
|
#include <hex/api/content_registry.hpp>
|
|
|
|
#include <hex/providers/provider.hpp>
|
|
|
|
#include <hex/helpers/encoding_file.hpp>
|
|
|
|
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <hex/ui/view.hpp>
|
|
|
|
|
2022-11-08 21:43:22 +01:00
|
|
|
namespace hex::plugin::builtin::ui {
|
2022-11-06 12:19:12 +01:00
|
|
|
|
|
|
|
class HexEditor {
|
|
|
|
public:
|
2022-11-08 21:43:22 +01:00
|
|
|
explicit HexEditor(prv::Provider *provider = nullptr);
|
2022-11-06 12:19:12 +01:00
|
|
|
~HexEditor();
|
2022-11-08 21:43:22 +01:00
|
|
|
void draw(float height = ImGui::GetContentRegionAvail().y);
|
2022-11-06 12:19:12 +01:00
|
|
|
|
2023-03-21 16:11:40 +01:00
|
|
|
void setProvider(prv::Provider *provider) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_provider = provider;
|
|
|
|
m_currValidRegion = { Region::Invalid(), false };
|
2023-03-21 16:11:40 +01:00
|
|
|
}
|
2023-12-19 13:10:25 +01:00
|
|
|
void setUnknownDataCharacter(char character) { m_unknownDataCharacter = character; }
|
2022-11-06 12:19:12 +01:00
|
|
|
private:
|
|
|
|
enum class CellType { None, Hex, ASCII };
|
|
|
|
|
2023-11-10 20:47:08 +01:00
|
|
|
void drawCell(u64 address, const u8 *data, size_t size, bool hovered, CellType cellType);
|
2023-12-17 18:26:36 +01:00
|
|
|
void drawSelectionFrame(u32 x, u32 y, Region selection, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize, const ImColor &backgroundColor) const;
|
2022-11-08 21:43:22 +01:00
|
|
|
void drawEditor(const ImVec2 &size);
|
|
|
|
void drawFooter(const ImVec2 &size);
|
2023-11-10 20:47:08 +01:00
|
|
|
void drawTooltip(u64 address, const u8 *data, size_t size) const;
|
2022-11-06 12:19:12 +01:00
|
|
|
|
|
|
|
void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered);
|
|
|
|
std::optional<color_t> applySelectionColor(u64 byteAddress, std::optional<color_t> color);
|
|
|
|
|
|
|
|
public:
|
2022-11-07 00:04:47 +01:00
|
|
|
void setSelectionUnchecked(std::optional<u64> start, std::optional<u64> end) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_selectionStart = start;
|
|
|
|
m_selectionEnd = end;
|
|
|
|
m_cursorPosition = end;
|
2022-11-07 00:04:47 +01:00
|
|
|
}
|
2022-11-06 12:19:12 +01:00
|
|
|
void setSelection(const Region ®ion) { this->setSelection(region.getStartAddress(), region.getEndAddress()); }
|
|
|
|
void setSelection(u128 start, u128 end) {
|
|
|
|
if (!ImHexApi::Provider::isValid())
|
|
|
|
return;
|
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
if (start > m_provider->getBaseAddress() + m_provider->getActualSize())
|
2023-11-11 23:00:37 +01:00
|
|
|
return;
|
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
if (start < m_provider->getBaseAddress())
|
2023-11-11 23:43:48 +01:00
|
|
|
return;
|
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
if (m_provider->getActualSize() == 0)
|
2023-11-12 00:20:30 +01:00
|
|
|
return;
|
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
const size_t maxAddress = m_provider->getActualSize() + m_provider->getBaseAddress() - 1;
|
2022-11-06 12:19:12 +01:00
|
|
|
|
2023-07-20 20:58:28 +02:00
|
|
|
constexpr static auto alignDown = [](u128 value, u128 alignment) {
|
|
|
|
return value & ~(alignment - 1);
|
|
|
|
};
|
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
m_selectionChanged = m_selectionStart != start || m_selectionEnd != end;
|
2023-07-21 14:22:53 +02:00
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
if (!m_selectionStart.has_value()) m_selectionStart = start;
|
|
|
|
if (!m_selectionEnd.has_value()) m_selectionEnd = end;
|
2023-07-20 20:58:28 +02:00
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
if (auto bytesPerCell = m_currDataVisualizer->getBytesPerCell(); bytesPerCell > 1) {
|
2023-07-20 20:58:28 +02:00
|
|
|
if (end > start) {
|
|
|
|
start = alignDown(start, bytesPerCell);
|
|
|
|
end = alignDown(end, bytesPerCell) + (bytesPerCell - 1);
|
|
|
|
} else {
|
|
|
|
start = alignDown(start, bytesPerCell) + (bytesPerCell - 1);
|
|
|
|
end = alignDown(end, bytesPerCell);
|
|
|
|
}
|
|
|
|
}
|
2022-11-06 12:19:12 +01:00
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
m_selectionStart = std::clamp<u128>(start, 0, maxAddress);
|
|
|
|
m_selectionEnd = std::clamp<u128>(end, 0, maxAddress);
|
|
|
|
m_cursorPosition = m_selectionEnd;
|
2022-11-06 12:19:12 +01:00
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
if (m_selectionChanged) {
|
2022-11-08 21:43:22 +01:00
|
|
|
auto selection = this->getSelection();
|
2023-12-19 13:10:25 +01:00
|
|
|
EventRegionSelected::post(ImHexApi::HexEditor::ProviderRegion{ { selection.address, selection.size }, m_provider });
|
|
|
|
m_shouldModifyValue = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] Region getSelection() const {
|
|
|
|
if (!isSelectionValid())
|
|
|
|
return Region::Invalid();
|
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
const auto start = std::min(m_selectionStart.value(), m_selectionEnd.value());
|
|
|
|
const auto end = std::max(m_selectionStart.value(), m_selectionEnd.value());
|
2022-11-06 12:19:12 +01:00
|
|
|
const size_t size = end - start + 1;
|
|
|
|
|
|
|
|
return { start, size };
|
|
|
|
}
|
|
|
|
|
2023-03-17 09:17:44 +01:00
|
|
|
[[nodiscard]] std::optional<u64> getCursorPosition() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_cursorPosition;
|
2023-03-17 09:17:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setCursorPosition(u64 cursorPosition) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_cursorPosition = cursorPosition;
|
2023-03-17 09:17:44 +01:00
|
|
|
}
|
|
|
|
|
2022-11-06 12:19:12 +01:00
|
|
|
[[nodiscard]] bool isSelectionValid() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_selectionStart.has_value() && m_selectionEnd.has_value();
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void jumpToSelection(bool center = true) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_shouldJumpToSelection = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
|
|
|
|
if (center)
|
2023-12-19 13:10:25 +01:00
|
|
|
m_centerOnJump = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void scrollToSelection() {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_shouldScrollToSelection = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void jumpIfOffScreen() {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_shouldJumpWhenOffScreen = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] u16 getBytesPerRow() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_bytesPerRow;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
2023-07-20 20:58:28 +02:00
|
|
|
[[nodiscard]] u16 getBytesPerCell() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_currDataVisualizer->getBytesPerCell();
|
2023-07-20 20:58:28 +02:00
|
|
|
}
|
|
|
|
|
2022-11-06 12:19:12 +01:00
|
|
|
void setBytesPerRow(u16 bytesPerRow) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_bytesPerRow = bytesPerRow;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] u16 getVisibleRowCount() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_visibleRowCount;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setSelectionColor(color_t color) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_selectionColor = color;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void enableUpperCaseHex(bool upperCaseHex) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_upperCaseHex = upperCaseHex;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void enableGrayOutZeros(bool grayOutZeros) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_grayOutZero = grayOutZeros;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void enableShowAscii(bool showAscii) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_showAscii = showAscii;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
2023-08-13 19:08:09 +02:00
|
|
|
void enableShowHumanReadableUnits(bool showHumanReadableUnits) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_showHumanReadableUnits = showHumanReadableUnits;
|
2023-08-13 19:08:09 +02:00
|
|
|
}
|
|
|
|
|
2022-11-06 12:19:12 +01:00
|
|
|
void enableSyncScrolling(bool syncScrolling) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_syncScrolling = syncScrolling;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setByteCellPadding(u32 byteCellPadding) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_byteCellPadding = byteCellPadding;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setCharacterCellPadding(u32 characterCellPadding) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_characterCellPadding = characterCellPadding;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
2023-01-28 21:12:35 +01:00
|
|
|
[[nodiscard]] const std::optional<EncodingFile>& getCustomEncoding() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_currCustomEncoding;
|
2023-01-28 21:12:35 +01:00
|
|
|
}
|
|
|
|
|
2023-03-31 19:56:20 +02:00
|
|
|
void setCustomEncoding(const EncodingFile &encoding) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_currCustomEncoding = encoding;
|
|
|
|
m_encodingLineStartAddresses.clear();
|
2023-03-31 19:56:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void setCustomEncoding(EncodingFile &&encoding) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_currCustomEncoding = std::move(encoding);
|
|
|
|
m_encodingLineStartAddresses.clear();
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void forceUpdateScrollPosition() {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_shouldUpdateScrollPosition = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
}
|
|
|
|
|
2022-11-07 00:04:47 +01:00
|
|
|
void setForegroundHighlightCallback(const std::function<std::optional<color_t>(u64, const u8 *, size_t)> &callback) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_foregroundColorCallback = callback;
|
2022-11-07 00:04:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setBackgroundHighlightCallback(const std::function<std::optional<color_t>(u64, const u8 *, size_t)> &callback) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_backgroundColorCallback = callback;
|
2022-11-07 00:04:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setTooltipCallback(const std::function<void(u64, const u8 *, size_t)> &callback) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_tooltipCallback = callback;
|
2022-11-07 00:04:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] float getScrollPosition() const {
|
2023-12-19 13:10:25 +01:00
|
|
|
return m_scrollPosition;
|
2022-11-07 00:04:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setScrollPosition(float scrollPosition) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_scrollPosition = scrollPosition;
|
2022-11-07 00:04:47 +01:00
|
|
|
}
|
|
|
|
|
2023-07-16 18:14:48 +02:00
|
|
|
void setEditingAddress(u64 address) {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_editingAddress = address;
|
|
|
|
m_shouldModifyValue = false;
|
|
|
|
m_enteredEditingMode = true;
|
2023-07-16 18:14:48 +02:00
|
|
|
|
2023-12-19 13:10:25 +01:00
|
|
|
m_editingBytes.resize(m_currDataVisualizer->getBytesPerCell());
|
|
|
|
m_provider->read(address + m_provider->getBaseAddress(), m_editingBytes.data(), m_editingBytes.size());
|
|
|
|
m_editingCellType = CellType::Hex;
|
2023-07-16 18:14:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void clearEditingAddress() {
|
2023-12-19 13:10:25 +01:00
|
|
|
m_editingAddress = std::nullopt;
|
2023-07-16 18:14:48 +02:00
|
|
|
}
|
|
|
|
|
2022-11-06 12:19:12 +01:00
|
|
|
private:
|
2022-11-08 21:43:22 +01:00
|
|
|
prv::Provider *m_provider;
|
|
|
|
|
2022-11-07 00:04:47 +01:00
|
|
|
std::optional<u64> m_selectionStart;
|
|
|
|
std::optional<u64> m_selectionEnd;
|
2023-03-17 09:17:44 +01:00
|
|
|
std::optional<u64> m_cursorPosition;
|
2022-11-07 00:04:47 +01:00
|
|
|
float m_scrollPosition = 0;
|
2022-11-06 12:19:12 +01:00
|
|
|
|
|
|
|
u16 m_bytesPerRow = 16;
|
2023-07-16 19:52:48 +02:00
|
|
|
std::endian m_dataVisualizerEndianness = std::endian::little;
|
2023-06-21 17:48:51 +02:00
|
|
|
std::shared_ptr<ContentRegistry::HexEditor::DataVisualizer> m_currDataVisualizer;
|
2023-02-15 17:01:36 +01:00
|
|
|
char m_unknownDataCharacter = '?';
|
2022-11-06 12:19:12 +01:00
|
|
|
|
|
|
|
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<u64> m_editingAddress;
|
|
|
|
bool m_shouldModifyValue = false;
|
|
|
|
bool m_enteredEditingMode = false;
|
|
|
|
bool m_shouldUpdateEditingValue = false;
|
|
|
|
std::vector<u8> m_editingBytes;
|
|
|
|
|
|
|
|
color_t m_selectionColor = 0x00;
|
|
|
|
bool m_upperCaseHex = true;
|
|
|
|
bool m_grayOutZero = true;
|
|
|
|
bool m_showAscii = true;
|
2023-03-31 13:49:33 +02:00
|
|
|
bool m_showCustomEncoding = true;
|
2023-08-13 19:08:09 +02:00
|
|
|
bool m_showHumanReadableUnits = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
bool m_syncScrolling = false;
|
|
|
|
u32 m_byteCellPadding = 0, m_characterCellPadding = 0;
|
2023-12-07 11:20:54 +01:00
|
|
|
bool m_footerCollapsed = true;
|
2022-11-06 12:19:12 +01:00
|
|
|
|
|
|
|
std::optional<EncodingFile> m_currCustomEncoding;
|
2023-03-14 09:35:43 +01:00
|
|
|
std::vector<u64> m_encodingLineStartAddresses;
|
2022-11-07 00:04:47 +01:00
|
|
|
|
2023-01-01 12:26:27 +01:00
|
|
|
std::pair<Region, bool> m_currValidRegion = { Region::Invalid(), false };
|
|
|
|
|
2023-11-10 20:47:08 +01:00
|
|
|
static std::optional<color_t> defaultColorCallback(u64, const u8 *, size_t) { return std::nullopt; }
|
|
|
|
static void defaultTooltipCallback(u64, const u8 *, size_t) { }
|
2022-11-07 00:04:47 +01:00
|
|
|
std::function<std::optional<color_t>(u64, const u8 *, size_t)> m_foregroundColorCallback = defaultColorCallback, m_backgroundColorCallback = defaultColorCallback;
|
|
|
|
std::function<void(u64, const u8 *, size_t)> m_tooltipCallback = defaultTooltipCallback;
|
2022-11-06 12:19:12 +01:00
|
|
|
};
|
|
|
|
|
2023-06-11 00:49:34 +02:00
|
|
|
}
|