#include "HexWidget.h" #include "Cutter.h" #include "Configuration.h" #include "dialogs/WriteCommandsDialogs.h" #include "dialogs/CommentsDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static constexpr uint64_t MAX_COPY_SIZE = 128 * 1024 * 1024; static constexpr int MAX_LINE_WIDTH_PRESET = 32; static constexpr int MAX_LINE_WIDTH_BYTES = 128 * 1024; static constexpr int WARNING_TIME_MS = 500; HexWidget::HexWidget(QWidget *parent) : QScrollArea(parent), cursorEnabled(true), cursorOnAscii(false), updatingSelection(false), itemByteLen(1), itemGroupSize(1), rowSizeBytes(16), columnMode(ColumnMode::PowerOf2), itemFormat(ItemFormatHex), itemBigEndian(false), addrCharLen(AddrWidth64), showHeader(true), showAscii(true), showExHex(true), showExAddr(true), warningTimer(this) { setMouseTracking(true); setFocusPolicy(Qt::FocusPolicy::StrongFocus); connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, [this]() { viewport()->update(); }); connect(Config(), &Configuration::colorsUpdated, this, &HexWidget::updateColors); connect(Config(), &Configuration::fontsUpdated, this, [this]() { setMonospaceFont(Config()->getFont()); }); auto sizeActionGroup = new QActionGroup(this); for (int i = 1; i <= 8; i *= 2) { QAction *action = new QAction(QString::number(i), this); action->setCheckable(true); action->setActionGroup(sizeActionGroup); connect(action, &QAction::triggered, this, [=]() { setItemSize(i); }); actionsItemSize.append(action); } actionsItemSize.at(0)->setChecked(true); /* Follow the order in ItemFormat enum */ QStringList names; names << tr("Hexadecimal"); names << tr("Octal"); names << tr("Decimal"); names << tr("Signed decimal"); names << tr("Float"); auto formatActionGroup = new QActionGroup(this); for (int i = 0; i < names.length(); ++i) { QAction *action = new QAction(names.at(i), this); action->setCheckable(true); action->setActionGroup(formatActionGroup); connect(action, &QAction::triggered, this, [=]() { setItemFormat(static_cast(i)); }); actionsItemFormat.append(action); } actionsItemFormat.at(0)->setChecked(true); actionsItemFormat.at(ItemFormatFloat)->setEnabled(false); rowSizeMenu = new QMenu(tr("Bytes per row"), this); auto columnsActionGroup = new QActionGroup(this); for (int i = 1; i <= MAX_LINE_WIDTH_PRESET; i *= 2) { QAction *action = new QAction(QString::number(i), rowSizeMenu); action->setCheckable(true); action->setActionGroup(columnsActionGroup); connect(action, &QAction::triggered, this, [=]() { setFixedLineSize(i); }); rowSizeMenu->addAction(action); } rowSizeMenu->addSeparator(); actionRowSizePowerOf2 = new QAction(tr("Power of 2"), this); actionRowSizePowerOf2->setCheckable(true); actionRowSizePowerOf2->setActionGroup(columnsActionGroup); connect(actionRowSizePowerOf2, &QAction::triggered, this, [=]() { setColumnMode(ColumnMode::PowerOf2); }); rowSizeMenu->addAction(actionRowSizePowerOf2); actionItemBigEndian = new QAction(tr("Big Endian"), this); actionItemBigEndian->setCheckable(true); actionItemBigEndian->setEnabled(false); connect(actionItemBigEndian, &QAction::triggered, this, &HexWidget::setItemEndianness); actionHexPairs = new QAction(tr("Bytes as pairs"), this); actionHexPairs->setCheckable(true); connect(actionHexPairs, &QAction::triggered, this, &HexWidget::onHexPairsModeEnabled); actionCopy = new QAction(tr("Copy"), this); addAction(actionCopy); actionCopy->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); actionCopy->setShortcut(QKeySequence::Copy); connect(actionCopy, &QAction::triggered, this, &HexWidget::copy); actionCopyAddress = new QAction(tr("Copy address"), this); actionCopyAddress->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); actionCopyAddress->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_C); connect(actionCopyAddress, &QAction::triggered, this, &HexWidget::copyAddress); addAction(actionCopyAddress); // Add comment option actionComment = new QAction(tr("Add Comment"), this); actionComment->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); actionComment->setShortcut(Qt::Key_Semicolon); connect(actionComment, &QAction::triggered, this, &HexWidget::onActionAddCommentTriggered); addAction(actionComment); // delete comment option actionDeleteComment = new QAction(tr("Delete Comment"), this); actionDeleteComment->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); connect(actionDeleteComment, &QAction::triggered, this, &HexWidget::onActionDeleteCommentTriggered); addAction(actionDeleteComment); actionSelectRange = new QAction(tr("Select range"), this); connect(actionSelectRange, &QAction::triggered, this, [this]() { rangeDialog.open(cursor.address); }); addAction(actionSelectRange); connect(&rangeDialog, &QDialog::accepted, this, &HexWidget::onRangeDialogAccepted); actionsWriteString.reserve(5); QAction *actionWriteString = new QAction(tr("Write string"), this); connect(actionWriteString, &QAction::triggered, this, &HexWidget::w_writeString); actionsWriteString.append(actionWriteString); QAction *actionWriteLenString = new QAction(tr("Write length and string"), this); connect(actionWriteLenString, &QAction::triggered, this, &HexWidget::w_writePascalString); actionsWriteString.append(actionWriteLenString); QAction *actionWriteWideString = new QAction(tr("Write wide string"), this); connect(actionWriteWideString, &QAction::triggered, this, &HexWidget::w_writeWideString); actionsWriteString.append(actionWriteWideString); QAction *actionWriteCString = new QAction(tr("Write zero terminated string"), this); connect(actionWriteCString, &QAction::triggered, this, &HexWidget::w_writeCString); actionsWriteString.append(actionWriteCString); QAction *actionWrite64 = new QAction(tr("Write a decoded or encoded Base64 string"), this); connect(actionWrite64, &QAction::triggered, this, &HexWidget::w_write64); actionsWriteString.append(actionWrite64); actionsWriteOther.reserve(5); QAction *actionWriteBytes = new QAction(tr("Write hex bytes"), this); connect(actionWriteBytes, &QAction::triggered, this, &HexWidget::w_writeBytes); actionsWriteOther.append(actionWriteBytes); QAction *actionWriteZeros = new QAction(tr("Write zeros"), this); connect(actionWriteZeros, &QAction::triggered, this, &HexWidget::w_writeZeros); actionsWriteOther.append(actionWriteZeros); QAction *actionWriteRandom = new QAction(tr("Write random bytes"), this); connect(actionWriteRandom, &QAction::triggered, this, &HexWidget::w_writeRandom); actionsWriteOther.append(actionWriteRandom); QAction *actionDuplicateFromOffset = new QAction(tr("Duplicate from offset"), this); connect(actionDuplicateFromOffset, &QAction::triggered, this, &HexWidget::w_duplFromOffset); actionsWriteOther.append(actionDuplicateFromOffset); QAction *actionIncDec = new QAction(tr("Increment/Decrement"), this); connect(actionIncDec, &QAction::triggered, this, &HexWidget::w_increaseDecrease); actionsWriteOther.append(actionIncDec); actionKeyboardEdit = new QAction(tr("Edit with keyboard"), this); actionKeyboardEdit->setCheckable(true); connect(actionKeyboardEdit, &QAction::triggered, this, &HexWidget::onKeyboardEditTriggered); connect(actionKeyboardEdit, &QAction::toggled, this, &HexWidget::onKeyboardEditChanged); connect(this, &HexWidget::selectionChanged, this, [this](Selection newSelection) { actionCopy->setEnabled(!newSelection.empty); }); updateMetrics(); updateItemLength(); startAddress = 0ULL; cursor.address = 0ULL; data.reset(new MemoryData()); oldData.reset(new MemoryData()); fetchData(); updateCursorMeta(); connect(&cursor.blinkTimer, &QTimer::timeout, this, &HexWidget::onCursorBlinked); cursor.setBlinkPeriod(1000); cursor.startBlinking(); updateColors(); warningTimer.setSingleShot(true); connect(&warningTimer, &QTimer::timeout, this, &HexWidget::hideWarningRect); } void HexWidget::setMonospaceFont(const QFont &font) { if (!(font.styleHint() & QFont::Monospace)) { /* FIXME: Use default monospace font setFont(XXX); */ } QScrollArea::setFont(font); monospaceFont = font.resolve(this->font()); updateMetrics(); fetchData(); updateCursorMeta(); viewport()->update(); } void HexWidget::setItemSize(int nbytes) { static const QVector values({ 1, 2, 4, 8 }); if (!values.contains(nbytes)) return; finishEditingWord(); itemByteLen = nbytes; if (itemByteLen > rowSizeBytes) { rowSizeBytes = itemByteLen; } actionsItemFormat.at(ItemFormatFloat)->setEnabled(nbytes >= 4); actionItemBigEndian->setEnabled(nbytes != 1); refreshWordEditState(); updateItemLength(); if (!cursorOnAscii && cursor.address % itemByteLen) { moveCursor(-int(cursor.address % itemByteLen)); } fetchData(); updateCursorMeta(); viewport()->update(); } void HexWidget::setItemFormat(ItemFormat format) { finishEditingWord(); itemFormat = format; bool sizeEnabled = true; if (format == ItemFormatFloat) sizeEnabled = false; actionsItemSize.at(0)->setEnabled(sizeEnabled); actionsItemSize.at(1)->setEnabled(sizeEnabled); refreshWordEditState(); updateItemLength(); fetchData(); updateCursorMeta(); viewport()->update(); } void HexWidget::setItemGroupSize(int size) { itemGroupSize = size; updateCounts(); fetchData(); updateCursorMeta(); viewport()->update(); } /** * @brief Checks if Item at the address changed compared to the last read data. * @param address Address of Item to be compared. * @return True if Item is different, False if Item is equal or last read didn't contain the * address. * @see HexWidget#readItem * * Checks if current Item at the address changed compared to the last read data. * It is assumed that the current read data buffer contains the address. */ bool HexWidget::isItemDifferentAt(uint64_t address) { char oldItem[sizeof(uint64_t)] = {}; char newItem[sizeof(uint64_t)] = {}; if (data->copy(newItem, address, static_cast(itemByteLen)) && oldData->copy(oldItem, address, static_cast(itemByteLen))) { return memcmp(oldItem, newItem, sizeof(oldItem)) != 0; } return false; } void HexWidget::updateCounts() { actionHexPairs->setEnabled(rowSizeBytes > 1 && itemByteLen == 1 && itemFormat == ItemFormat::ItemFormatHex); actionHexPairs->setChecked(Core()->getConfigb("hex.pairs")); if (actionHexPairs->isChecked() && actionHexPairs->isEnabled()) { itemGroupSize = 2; } else { itemGroupSize = 1; } if (columnMode == ColumnMode::PowerOf2) { int last_good_size = itemGroupByteLen(); for (int i = itemGroupByteLen(); i <= MAX_LINE_WIDTH_BYTES; i *= 2) { rowSizeBytes = i; itemColumns = rowSizeBytes / itemGroupByteLen(); updateAreasPosition(); if (horizontalScrollBar()->maximum() == 0) { last_good_size = rowSizeBytes; } else { break; } } rowSizeBytes = last_good_size; } itemColumns = rowSizeBytes / itemGroupByteLen(); // ensure correct action is selected when changing line size programmatically if (columnMode == ColumnMode::Fixed) { int w = 1; const auto &actions = rowSizeMenu->actions(); for (auto action : actions) { action->setChecked(false); } for (auto action : actions) { if (w > MAX_LINE_WIDTH_PRESET) { break; } if (rowSizeBytes == w) { action->setChecked(true); } w *= 2; } } else if (columnMode == ColumnMode::PowerOf2) { actionRowSizePowerOf2->setChecked(true); } updateAreasPosition(); } void HexWidget::setFixedLineSize(int lineSize) { if (lineSize < 1 || lineSize < itemGroupByteLen() || lineSize % itemGroupByteLen()) { updateCounts(); return; } rowSizeBytes = lineSize; columnMode = ColumnMode::Fixed; updateCounts(); fetchData(); updateCursorMeta(); viewport()->update(); } void HexWidget::setColumnMode(ColumnMode mode) { columnMode = mode; updateCounts(); fetchData(); updateCursorMeta(); viewport()->update(); } void HexWidget::selectRange(RVA start, RVA end) { BasicCursor endCursor(end); endCursor += 1; setCursorAddr(endCursor); selection.set(start, end); cursorEnabled = false; emit selectionChanged(getSelection()); } void HexWidget::clearSelection() { setCursorAddr(BasicCursor(cursor.address), false); emit selectionChanged(getSelection()); } HexWidget::Selection HexWidget::getSelection() { return Selection { selection.isEmpty(), selection.start(), selection.end() }; } void HexWidget::seek(uint64_t address) { if (!cursorOnAscii) { // when other widget causes seek to the middle of word // switch to ascii column which operates with byte positions auto viewOffset = startAddress % itemByteLen; auto addrOffset = address % itemByteLen; if ((addrOffset + itemByteLen - viewOffset) % itemByteLen) { setCursorOnAscii(true); } } setCursorAddr(BasicCursor(address)); } void HexWidget::refresh() { fetchData(); viewport()->update(); } void HexWidget::setItemEndianness(bool bigEndian) { finishEditingWord(); itemBigEndian = bigEndian; updateCursorMeta(); // Update cached item character viewport()->update(); } void HexWidget::updateColors() { borderColor = Config()->getColor("gui.border"); backgroundColor = Config()->getColor("gui.background"); b0x00Color = Config()->getColor("b0x00"); b0x7fColor = Config()->getColor("b0x7f"); b0xffColor = Config()->getColor("b0xff"); printableColor = Config()->getColor("ai.write"); defColor = Config()->getColor("btext"); addrColor = Config()->getColor("func_var_addr"); diffColor = Config()->getColor("graph.diff.unmatch"); warningColor = QColor("red"); updateCursorMeta(); viewport()->update(); } void HexWidget::paintEvent(QPaintEvent *event) { QPainter painter(viewport()); painter.setFont(monospaceFont); int xOffset = horizontalScrollBar()->value(); if (xOffset > 0) painter.translate(QPoint(-xOffset, 0)); if (event->rect() == cursor.screenPos.toAlignedRect()) { /* Cursor blink */ drawCursor(painter); return; } painter.fillRect(event->rect().translated(xOffset, 0), backgroundColor); drawHeader(painter); drawAddrArea(painter); drawItemArea(painter); drawAsciiArea(painter); if (warningRectVisible) { painter.setPen(warningColor); painter.drawRect(warningRect); } if (!cursorEnabled) return; drawCursor(painter, true); } void HexWidget::updateWidth() { int max = (showAscii ? asciiArea.right() : itemArea.right()) - viewport()->width(); if (max < 0) max = 0; else max += charWidth; horizontalScrollBar()->setMaximum(max); horizontalScrollBar()->setSingleStep(charWidth); } bool HexWidget::isFixedWidth() const { return itemFormat == ItemFormatHex || itemFormat == ItemFormatOct; } void HexWidget::resizeEvent(QResizeEvent *event) { int oldByteCount = bytesPerScreen(); updateCounts(); if (event->oldSize().height() == event->size().height() && oldByteCount == bytesPerScreen()) return; updateAreasHeight(); fetchData(); // rowCount was changed updateCursorMeta(); viewport()->update(); } void HexWidget::mouseMoveEvent(QMouseEvent *event) { QPoint pos = event->pos(); pos.rx() += horizontalScrollBar()->value(); auto mouseAddr = mousePosToAddr(pos).address; QString metaData = getFlagsAndComment(mouseAddr); if (!metaData.isEmpty() && itemArea.contains(pos)) { QToolTip::showText(mapToGlobal(event->pos()), metaData.replace(",", ", "), this); } else { QToolTip::hideText(); } if (!updatingSelection) { if (itemArea.contains(pos) || asciiArea.contains(pos)) setCursor(Qt::IBeamCursor); else setCursor(Qt::ArrowCursor); return; } auto &area = currentArea(); if (pos.x() < area.left()) pos.setX(area.left()); else if (pos.x() > area.right()) pos.setX(area.right()); auto addr = currentAreaPosToAddr(pos, true); setCursorAddr(addr, true); /* Stop blinking */ cursorEnabled = false; viewport()->update(); } void HexWidget::mousePressEvent(QMouseEvent *event) { QPoint pos(event->pos()); pos.rx() += horizontalScrollBar()->value(); if (event->button() == Qt::LeftButton) { bool selectingData = itemArea.contains(pos); bool selecting = selectingData || asciiArea.contains(pos); bool holdingShift = event->modifiers() == Qt::ShiftModifier; // move cursor within actively edited item if (selectingData && !holdingShift && editWordState >= EditWordState::WriteNotEdited) { auto editWordArea = itemRectangle(cursor.address - startAddress); if (editWordArea.contains(pos)) { int wordOffset = 0; auto cursorPosition = screenPosToAddr(pos, false, &wordOffset); if (cursorPosition.address == cursor.address // allow selecting after last character only when cursor limited to current word && (wordOffset < editWord.length() || navigationMode == HexNavigationMode::WordChar)) { editWordPos = std::max(0, wordOffset); editWordPos = std::min(editWordPos, editWord.length()); if (isFixedWidth()) { updatingSelection = true; auto selectionCursor = cursorPosition; if (editWordPos > itemCharLen / 2) { selectionCursor += itemByteLen; } selection.init(selectionCursor); } viewport()->update(); return; } } } // cursor within any item if the mode allows if (selectingData && !holdingShift && editWordState >= EditWordState::WriteNotStarted && navigationMode == HexNavigationMode::AnyChar) { updatingSelection = true; setCursorOnAscii(false); int wordOffset = 0; auto cursorPosition = screenPosToAddr(pos, false, &wordOffset); finishEditingWord(); if (isFixedWidth() && wordOffset >= itemCharLen - itemPrefixLen) { wordOffset = 0; cursorPosition += itemByteLen; } setCursorAddr(cursorPosition, holdingShift); auto selectionPosition = currentAreaPosToAddr(pos, true); selection.init(selectionPosition); emit selectionChanged(getSelection()); if (wordOffset > 0) { startEditWord(); editWordPos = std::min(wordOffset, editWord.length() - 1); } viewport()->update(); return; } if (selecting) { finishEditingWord(); updatingSelection = true; setCursorOnAscii(!selectingData); auto cursorPosition = currentAreaPosToAddr(pos, true); setCursorAddr(cursorPosition, holdingShift); viewport()->update(); } } } void HexWidget::mouseDoubleClickEvent(QMouseEvent *event) { QPoint pos(event->pos()); pos.rx() += horizontalScrollBar()->value(); if (event->button() == Qt::LeftButton && !isFixedWidth() && editWordState == EditWordState::WriteNotStarted && itemArea.contains(pos)) { int wordOffset = 0; auto cursorPosition = screenPosToAddr(pos, false, &wordOffset); setCursorAddr(cursorPosition, false); startEditWord(); int padding = std::max(0, itemCharLen - editWord.length()); editWordPos = std::max(0, wordOffset - padding); editWordPos = std::min(editWordPos, editWord.length()); } } void HexWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { if (selection.isEmpty()) { selection.init(BasicCursor(cursor.address)); cursorEnabled = true; viewport()->update(); } updatingSelection = false; } } void HexWidget::wheelEvent(QWheelEvent *event) { // according to Qt doc 1 row per 5 degrees, angle measured in 1/8 of degree int dy = event->angleDelta().y() / (8 * 5); int64_t delta = -dy * itemRowByteLen(); if (dy == 0) return; if (delta < 0 && startAddress < static_cast(-delta)) { startAddress = 0; } else if (delta > 0 && data->maxIndex() < static_cast(bytesPerScreen())) { startAddress = 0; } else if ((data->maxIndex() - startAddress) <= static_cast(bytesPerScreen() + delta - 1)) { startAddress = (data->maxIndex() - bytesPerScreen()) + 1; } else { startAddress += delta; } fetchData(); if (cursor.address >= startAddress && cursor.address <= lastVisibleAddr()) { /* Don't enable cursor blinking if selection isn't empty */ cursorEnabled = selection.isEmpty(); updateCursorMeta(); } else { cursorEnabled = false; } viewport()->update(); } bool HexWidget::validCharForEdit(QChar digit) { switch (itemFormat) { case ItemFormatHex: return (digit >= '0' && digit <= '9') || (digit >= 'a' && digit <= 'f') || (digit >= 'A' && digit <= 'F'); case ItemFormatOct: { if (editWordPos > 0) { return (digit >= '0' && digit <= '7'); } else { int bitsInMSD = (itemByteLen * 8) % 3; int biggestDigit = (1 << bitsInMSD) - 1; return digit >= '0' && digit <= char('0' + biggestDigit); } } case ItemFormatDec: return (digit >= '0' && digit <= '9'); case ItemFormatSignedDec: return (digit >= '0' && digit <= '9') || digit == '-'; case ItemFormatFloat: return (digit >= '0' && digit <= '9') || digit == '-' || digit == '+' || digit == '.' || digit == ',' || digit == '+' || digit == 'e' || digit == 'E' || digit == 'i' || digit == 'n' || digit == 'f' || digit == 'I' || digit == 'N' || digit == 'F' || digit == 'a' || digit == 'A'; } return false; } void HexWidget::movePrevEditCharAny() { if (!selection.isEmpty()) { clearSelection(); } editWordPos -= 1; if (editWordPos < 0) { finishEditingWord(); if (moveCursor(-itemByteLen, false, OverflowMove::Ignore)) { startEditWord(); editWordPos = editWord.length() - 1; } } viewport()->update(); } void HexWidget::typeOverwriteModeChar(QChar c) { if (editWordState < EditWordState::WriteNotEdited || !isFixedWidth()) { return; } editWord[editWordPos] = c; editWordPos++; editWordState = EditWordState::WriteEdited; if (editWordPos >= editWord.length()) { finishEditingWord(); bool moved = moveCursor(itemByteLen, false, OverflowMove::Ignore); startEditWord(); if (!moved) { editWordPos = editWord.length() - 1; } } } HexWidget::HexNavigationMode HexWidget::defaultNavigationMode() { switch (editWordState) { case EditWordState::Read: return HexNavigationMode::Words; case EditWordState::WriteNotStarted: return isFixedWidth() ? HexNavigationMode::AnyChar : HexNavigationMode::Words; case EditWordState::WriteNotEdited: case EditWordState::WriteEdited: return isFixedWidth() ? HexNavigationMode::AnyChar : HexNavigationMode::WordChar; } return HexNavigationMode::Words; } void HexWidget::refreshWordEditState() { navigationMode = defaultNavigationMode(); } bool HexWidget::handleAsciiWrite(QKeyEvent *event) { if (!cursorOnAscii || !canKeyboardEdit()) { return false; } if (event->key() == Qt::Key_Backspace || event->matches(QKeySequence::Backspace)) { if (!selection.isEmpty()) { writeZeros(selection.start(), selection.size()); } else { moveCursor(-1, false); writeZeros(cursor.address, 1); } return true; } if (event->key() == Qt::Key_Delete || event->matches(QKeySequence::Delete)) { if (!selection.isEmpty()) { writeZeros(selection.start(), selection.size()); } else { writeZeros(cursor.address, 1); moveCursor(1, false); } return true; } QString text; if (event->matches(QKeySequence::Paste)) { text = QApplication::clipboard()->text(); if (text.length() <= 0) { return false; } } else { text = event->text(); if (text.length() <= 0) { return false; } QChar c = text[0]; if (c <= '\x1f' || c == '\x7f') { return false; } } auto bytes = text.toUtf8(); // TODO:#3028 use selected text encoding auto address = getLocationAddress(); clearSelection(); data->write(reinterpret_cast(bytes.data()), address, bytes.length()); seek(address + bytes.length()); viewport()->update(); return true; } bool HexWidget::handleNumberWrite(QKeyEvent *event) { if (editWordState < EditWordState::WriteNotStarted) { return false; } bool overwrite = isFixedWidth(); auto keyText = event->text(); bool editingWord = editWordState >= EditWordState::WriteNotEdited; if (keyText.length() > 0 && validCharForEdit(keyText[0])) { if (!selection.isEmpty()) { setCursorAddr(BasicCursor(selection.start())); } if (!editingWord) { startEditWord(); } if (overwrite) { typeOverwriteModeChar(keyText[0]); } else if (!editingWord /* && !overwrite */) { editWord = keyText; editWordPos = editWord.length(); editWordState = EditWordState::WriteEdited; } else if (itemFormat == ItemFormatFloat || editWord.length() < itemCharLen) { editWord.insert(editWordPos, keyText); editWordPos += keyText.length(); editWordState = EditWordState::WriteEdited; } maybeFlushCharEdit(); return true; } if (event->matches(QKeySequence::Paste) && (editingWord || overwrite)) { QString text = QApplication::clipboard()->text(); if (text.length() > 0) { if (overwrite) { startEditWord(); for (QChar c : text) { if (validCharForEdit(c)) { typeOverwriteModeChar(c); } } } else { editWord.insert(editWordPos, text); editWordPos += text.length(); editWordState = EditWordState::WriteEdited; } } maybeFlushCharEdit(); return true; } if (editingWord) { if (event->matches(QKeySequence::Cancel)) { cancelEditedWord(); return true; } else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { bool needToAdvance = !(editWordPos == 0 && overwrite && editWordState < EditWordState::WriteEdited); if (finishEditingWord(false) && needToAdvance) { moveCursor(itemByteLen); } return true; } } if (event->matches(QKeySequence::Delete)) { if (!selection.isEmpty()) { writeZeros(selection.start(), selection.size()); clearSelection(); } else { startEditWord(); if (overwrite) { typeOverwriteModeChar('0'); } else if (editWordPos < editWord.length()) { editWord.remove(editWordPos, 1); editWordState = EditWordState::WriteEdited; } } maybeFlushCharEdit(); return true; } if (event->matches(QKeySequence::DeleteEndOfWord) && selection.isEmpty()) { startEditWord(); if (overwrite) { for (int i = editWordPos; i < editWord.length(); i++) { typeOverwriteModeChar('0'); } } else if (editWordPos < editWord.length()) { editWord.remove(editWordPos, editWord.length()); editWordState = EditWordState::WriteEdited; } maybeFlushCharEdit(); return true; } if (event->matches(QKeySequence::DeleteStartOfWord) && selection.isEmpty()) { if (!editingWord || (overwrite && editWordPos == 0)) { if (!moveCursor(-itemByteLen, false, OverflowMove::Ignore)) { return false; } startEditWord(); editWordPos = editWord.length(); } if (overwrite) { while (editWordPos > 0) { editWordPos--; editWord[editWordPos] = '0'; } editWordState = EditWordState::WriteEdited; maybeFlushCharEdit(); return true; } else { if (editWordPos > 0) { editWord.remove(0, editWordPos); editWordState = EditWordState::WriteEdited; editWordPos = 0; maybeFlushCharEdit(); return true; } } return false; } if (event->key() == Qt::Key_Backspace) { if (!selection.isEmpty()) { writeZeros(selection.start(), selection.size()); clearSelection(); setCursorAddr(BasicCursor(selection.start()), false); return true; } else { if (!editingWord || (overwrite && editWordPos == 0)) { if (!moveCursor(-itemByteLen, false, OverflowMove::Ignore)) { return false; } startEditWord(); editWordPos = editWord.length(); } if (editWordPos > 0) { editWordPos -= 1; if (overwrite) { editWord[editWordPos] = '0'; } else { editWord.remove(editWordPos, 1); } editWordState = EditWordState::WriteEdited; maybeFlushCharEdit(); return true; } } return false; } return false; } bool HexWidget::event(QEvent *event) { // prefer treating keys like 's' 'g' '.' as typing input instead of global shortcuts if (event->type() == QEvent::ShortcutOverride) { auto keyEvent = static_cast(event); auto modifiers = keyEvent->modifiers(); if ((modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier || modifiers == Qt::KeypadModifier) && keyEvent->key() < Qt::Key_Escape && canKeyboardEdit()) { keyEvent->accept(); return true; } } return QScrollArea::event(event); } void HexWidget::keyPressEvent(QKeyEvent *event) { bool select = false; auto moveOrSelect = [event, &select](QKeySequence::StandardKey moveSeq, QKeySequence::StandardKey selectSeq) -> bool { if (event->matches(moveSeq)) { select = false; return true; } else if (event->matches(selectSeq)) { select = true; return true; } return false; }; if (canKeyboardEdit()) { if (handleAsciiWrite(event)) { viewport()->update(); return; } if (editWordState >= EditWordState::WriteNotStarted && !cursorOnAscii) { if (handleNumberWrite(event)) { viewport()->update(); return; } } } if (cursorOnAscii || navigationMode == HexNavigationMode::Words || navigationMode == HexNavigationMode::AnyChar) { if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { moveCursor(bytesPerScreen(), select); } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, QKeySequence::SelectPreviousPage)) { moveCursor(-bytesPerScreen(), select); } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { int linePos = int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); moveCursor(-linePos, select); } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { int linePos = int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); moveCursor(itemRowByteLen() - linePos, select); } } if (navigationMode == HexNavigationMode::Words || cursorOnAscii) { if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { moveCursor(itemRowByteLen(), select, OverflowMove::Ignore); } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, QKeySequence::SelectPreviousLine)) { moveCursor(-itemRowByteLen(), select, OverflowMove::Ignore); } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar) || moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { moveCursor(cursorOnAscii ? 1 : itemByteLen, select); } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar) || moveOrSelect(QKeySequence::MoveToPreviousWord, QKeySequence::SelectPreviousWord)) { moveCursor(cursorOnAscii ? -1 : -itemByteLen, select); } } else if (navigationMode == HexNavigationMode::AnyChar && !cursorOnAscii) { if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { moveCursorKeepEditOffset(itemRowByteLen(), select, OverflowMove::Ignore); } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, QKeySequence::SelectPreviousLine)) { moveCursorKeepEditOffset(-itemRowByteLen(), select, OverflowMove::Ignore); } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) { if (select) { moveCursor(itemByteLen, select); } else { if (!selection.isEmpty()) { clearSelection(); } if (editWordState == EditWordState::WriteNotStarted) { startEditWord(); } editWordPos += 1; if (editWordPos >= editWord.length()) { bool moved = moveCursor(itemByteLen, false, OverflowMove::Ignore); startEditWord(); if (!moved) { editWordPos = editWord.length() - 1; } } } viewport()->update(); } else if (event->matches(QKeySequence::MoveToPreviousChar)) { movePrevEditCharAny(); } else if (event->matches(QKeySequence::SelectPreviousChar)) { moveCursor(-itemByteLen, true); } else if (moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { moveCursor(itemByteLen, select); } else if (event->matches(QKeySequence::MoveToPreviousWord)) { if (editWordPos > 0) { editWordPos = 0; viewport()->update(); } else { moveCursor(-itemByteLen, false); } } else if (event->matches(QKeySequence::SelectPreviousWord)) { moveCursor(-itemByteLen, true); } } else if (navigationMode == HexNavigationMode::WordChar) { if (event->matches(QKeySequence::MoveToNextChar)) { editWordPos = std::min(editWord.length(), editWordPos + 1); viewport()->update(); } else if (event->matches(QKeySequence::MoveToPreviousChar)) { editWordPos = std::max(0, editWordPos - 1); viewport()->update(); } else if (event->matches(QKeySequence::MoveToStartOfLine)) { editWordPos = 0; viewport()->update(); } else if (event->matches(QKeySequence::MoveToEndOfLine)) { editWordPos = editWord.length(); viewport()->update(); } else if (event->matches(QKeySequence::MoveToPreviousWord)) { if (editWordPos > 0) { editWordPos = 0; } else { moveCursor(-itemByteLen, select); startEditWord(); } viewport()->update(); } else if (event->matches(QKeySequence::MoveToNextWord)) { if (editWordPos < editWord.length()) { editWordPos = editWord.length(); } else { moveCursor(itemByteLen, select); startEditWord(); editWordPos = editWord.length(); } viewport()->update(); } } } void HexWidget::contextMenuEvent(QContextMenuEvent *event) { QPoint pt = event->pos(); bool mouseOutsideSelection = false; if (event->reason() == QContextMenuEvent::Mouse) { auto mouseAddr = mousePosToAddr(pt).address; if (asciiArea.contains(pt)) { cursorOnAscii = true; } else if (itemArea.contains(pt)) { cursorOnAscii = false; } if (selection.isEmpty()) { seek(mouseAddr); } else { mouseOutsideSelection = !selection.contains(mouseAddr); } } auto disableOutsideSelectionActions = [this](bool disable) { actionCopyAddress->setDisabled(disable); }; QString comment = Core()->getCommentAt(cursor.address); if (comment.isNull() || comment.isEmpty()) { actionDeleteComment->setVisible(false); actionComment->setText(tr("Add Comment")); } else { actionDeleteComment->setVisible(true); actionComment->setText(tr("Edit Comment")); } if (!ioModesController.canWrite()) { actionKeyboardEdit->setChecked(false); } auto *menu = new QMenu(this); QMenu *sizeMenu = menu->addMenu(tr("Item size:")); sizeMenu->addActions(actionsItemSize); QMenu *formatMenu = menu->addMenu(tr("Item format:")); formatMenu->addActions(actionsItemFormat); menu->addMenu(rowSizeMenu); menu->addAction(actionHexPairs); menu->addAction(actionItemBigEndian); QMenu *writeMenu = menu->addMenu(tr("Edit")); writeMenu->addActions(actionsWriteString); writeMenu->addSeparator(); writeMenu->addActions(actionsWriteOther); menu->addAction(actionKeyboardEdit); menu->addSeparator(); menu->addAction(actionCopy); disableOutsideSelectionActions(mouseOutsideSelection); menu->addAction(actionCopyAddress); menu->addActions(this->actions()); menu->exec(mapToGlobal(pt)); disableOutsideSelectionActions(false); menu->deleteLater(); } void HexWidget::onCursorBlinked() { if (!cursorEnabled) return; cursor.blink(); QRect cursorRect = cursor.screenPos.toAlignedRect(); viewport()->update(cursorRect.translated(-horizontalScrollBar()->value(), 0)); } void HexWidget::onHexPairsModeEnabled(bool enable) { // Sync configuration Core()->setConfig("hex.pairs", enable); if (enable) { setItemGroupSize(2); } else { setItemGroupSize(1); } } void HexWidget::copy() { if (selection.isEmpty() || selection.size() > MAX_COPY_SIZE) return; auto x = cursorOnAscii ? Core()->getString(selection.start(), selection.size(), RZ_STRING_ENC_8BIT, true) : Core()->ioRead(selection.start(), (int)selection.size()).toHex(); QApplication::clipboard()->setText(x); } void HexWidget::copyAddress() { uint64_t addr = getLocationAddress(); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(RzAddressString(addr)); } // slot for add comment action void HexWidget::onActionAddCommentTriggered() { uint64_t addr = cursor.address; CommentsDialog::addOrEditComment(addr, this); } // slot for deleting comment action void HexWidget::onActionDeleteCommentTriggered() { uint64_t addr = cursor.address; Core()->delComment(addr); } void HexWidget::onRangeDialogAccepted() { if (rangeDialog.empty()) { seek(rangeDialog.getStartAddress()); return; } selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress()); } void HexWidget::writeZeros(uint64_t address, uint64_t length) { const uint64_t MAX_BUFFER = 1024; std::vector zeroes(std::min(MAX_BUFFER, length), 0); while (length > zeroes.size()) { data->write(zeroes.data(), address, zeroes.size()); address += zeroes.size(); length -= zeroes.size(); } if (length > 0) { data->write(zeroes.data(), address, length); } } void HexWidget::w_writeString() { if (!ioModesController.prepareForWriting()) { return; } bool ok = false; QString str = QInputDialog::getText(this, tr("Write string"), tr("String:"), QLineEdit::Normal, "", &ok); if (!ok || str.isEmpty()) { return; } { RzCoreLocked core(Core()); rz_core_write_string_at(core, getLocationAddress(), str.toUtf8().constData()); } refresh(); } void HexWidget::w_increaseDecrease() { if (!ioModesController.prepareForWriting()) { return; } IncrementDecrementDialog d; int ret = d.exec(); if (ret == QDialog::Rejected) { return; } int64_t value = (int64_t)d.getValue(); uint8_t sz = d.getNBytes(); if (d.getMode() == IncrementDecrementDialog::Decrease) { value *= -1; } { RzCoreLocked core(Core()); rz_core_write_value_inc_at(core, getLocationAddress(), value, sz); } refresh(); } void HexWidget::w_writeBytes() { if (!ioModesController.prepareForWriting()) { return; } bool ok = false; int size = INT_MAX; if (!selection.isEmpty() && selection.size() <= INT_MAX) { size = static_cast(selection.size()); } QByteArray bytes = QInputDialog::getText(this, tr("Write hex bytes"), tr("Hex byte string:"), QLineEdit::Normal, "", &ok) .toUtf8(); const int offset = bytes.startsWith("\\x") ? 2 : 0; const int incr = offset + 2; const int bytes_size = qMin(bytes.size() / incr, size); if (!ok || !bytes_size) { return; } { auto *buf = (uint8_t *)malloc(static_cast(bytes_size)); if (!buf) { return; } for (int i = 0, j = 0, sz = bytes.size(); i < sz; i += incr, j++) { buf[j] = static_cast(bytes.mid(i + offset, 2).toInt(nullptr, 16)); } RzCoreLocked core(Core()); rz_core_write_at(core, getLocationAddress(), buf, bytes_size); free(buf); } refresh(); } void HexWidget::w_writeZeros() { if (!ioModesController.prepareForWriting()) { return; } int size = 1; if (!selection.isEmpty() && selection.size() <= INT_MAX) { size = static_cast(selection.size()); } bool ok = false; int len = QInputDialog::getInt(this, tr("Write zeros"), tr("Number of zeros:"), size, 1, 0x7FFFFFFF, 1, &ok); if (!ok) { return; } { writeZeros(getLocationAddress(), len); } refresh(); } void HexWidget::w_write64() { if (!ioModesController.prepareForWriting()) { return; } Base64EnDecodedWriteDialog d; int ret = d.exec(); if (ret == QDialog::Rejected) { return; } QByteArray str = d.getData(); if (d.getMode() == Base64EnDecodedWriteDialog::Decode && (QString(str).contains(QRegularExpression("[^a-zA-Z0-9+/=]")) || str.length() % 4 != 0 || str.isEmpty())) { QMessageBox::critical( this, tr("Error"), tr("Error occured during decoding your input.\n" "Please, make sure, that it is a valid base64 string and try again.")); return; } { RzCoreLocked core(Core()); if (d.getMode() == Base64EnDecodedWriteDialog::Encode) { rz_core_write_base64_at(core, getLocationAddress(), str.toHex().constData()); } else { rz_core_write_base64d_at(core, getLocationAddress(), str.constData()); } } refresh(); } void HexWidget::w_writeRandom() { if (!ioModesController.prepareForWriting()) { return; } int size = 1; if (!selection.isEmpty() && selection.size() <= INT_MAX) { size = static_cast(selection.size()); } bool ok = false; int nbytes = QInputDialog::getInt(this, tr("Write random bytes"), tr("Number of bytes:"), size, 1, 0x7FFFFFFF, 1, &ok); if (!ok) { return; } { RzCoreLocked core(Core()); rz_core_write_random_at(core, getLocationAddress(), nbytes); } refresh(); } void HexWidget::w_duplFromOffset() { if (!ioModesController.prepareForWriting()) { return; } DuplicateFromOffsetDialog d; int ret = d.exec(); if (ret == QDialog::Rejected) { return; } RVA src = d.getOffset(); int len = (int)d.getNBytes(); { RzCoreLocked core(Core()); rz_core_write_duplicate_at(core, getLocationAddress(), src, len); } refresh(); } void HexWidget::w_writePascalString() { if (!ioModesController.prepareForWriting()) { return; } bool ok = false; QString str = QInputDialog::getText(this, tr("Write Pascal string"), tr("String:"), QLineEdit::Normal, "", &ok); if (!ok || str.isEmpty()) { return; } { RzCoreLocked core(Core()); rz_core_write_length_string_at(core, getLocationAddress(), str.toUtf8().constData()); } refresh(); } void HexWidget::w_writeWideString() { if (!ioModesController.prepareForWriting()) { return; } bool ok = false; QString str = QInputDialog::getText(this, tr("Write wide string"), tr("String:"), QLineEdit::Normal, "", &ok); if (!ok || str.isEmpty()) { return; } { RzCoreLocked core(Core()); rz_core_write_string_wide_at(core, getLocationAddress(), str.toUtf8().constData()); } refresh(); } void HexWidget::w_writeCString() { if (!ioModesController.prepareForWriting()) { return; } bool ok = false; QString str = QInputDialog::getText(this, tr("Write zero-terminated string"), tr("String:"), QLineEdit::Normal, "", &ok); if (!ok || str.isEmpty()) { return; } { RzCoreLocked core(Core()); rz_core_write_string_zero_at(core, getLocationAddress(), str.toUtf8().constData()); } refresh(); } void HexWidget::onKeyboardEditTriggered(bool enabled) { if (!enabled) { return; } if (!ioModesController.prepareForWriting()) { actionKeyboardEdit->setChecked(false); } } void HexWidget::onKeyboardEditChanged(bool enabled) { if (!enabled) { finishEditingWord(); navigationMode = HexNavigationMode::Words; editWordState = EditWordState::Read; } else { editWordState = EditWordState::WriteNotStarted; navigationMode = defaultNavigationMode(); } updateCursorMeta(); viewport()->update(); } void HexWidget::updateItemLength() { itemPrefixLen = 0; itemPrefix.clear(); switch (itemFormat) { case ItemFormatHex: itemCharLen = 2 * itemByteLen; if (itemByteLen > 1 && showExHex) { itemPrefixLen = hexPrefix.length(); itemPrefix = hexPrefix; } break; case ItemFormatOct: itemCharLen = (itemByteLen * 8 + 3) / 3; break; case ItemFormatDec: switch (itemByteLen) { case 1: itemCharLen = 3; break; case 2: itemCharLen = 5; break; case 4: itemCharLen = 10; break; case 8: itemCharLen = 20; break; } break; case ItemFormatSignedDec: switch (itemByteLen) { case 1: itemCharLen = 4; break; case 2: itemCharLen = 6; break; case 4: itemCharLen = 11; break; case 8: itemCharLen = 20; break; } break; case ItemFormatFloat: if (itemByteLen < 4) itemByteLen = 4; // FIXME itemCharLen = 3 * itemByteLen; break; } itemCharLen += itemPrefixLen; updateCounts(); } void HexWidget::drawHeader(QPainter &painter) { if (!showHeader) return; int offset = 0; QRectF rect(itemArea.left(), 0, itemWidth(), lineHeight); painter.setPen(addrColor); for (int j = 0; j < itemColumns; ++j) { for (int k = 0; k < itemGroupSize; ++k, offset += itemByteLen) { painter.drawText(rect, Qt::AlignVCenter | Qt::AlignRight, QString::number(offset, 16).toUpper()); rect.translate(itemWidth(), 0); } rect.translate(columnSpacingWidth(), 0); } rect.moveLeft(asciiArea.left()); rect.setWidth(charWidth); for (int j = 0; j < itemRowByteLen(); ++j) { painter.drawText(rect, Qt::AlignVCenter | Qt::AlignRight, QString::number(j % 16, 16).toUpper()); rect.translate(charWidth, 0); } } void HexWidget::drawCursor(QPainter &painter, bool shadow) { if (shadow) { QPen pen(Qt::gray); pen.setStyle(Qt::DashLine); painter.setPen(pen); qreal shadowWidth = charWidth; if (cursorOnAscii) { shadowWidth = itemWidth(); } else if (editWordState >= EditWordState::WriteNotEdited) { shadowWidth = itemByteLen * charWidth; } shadowCursor.screenPos.setWidth(shadowWidth); painter.drawRect(shadowCursor.screenPos); painter.setPen(Qt::SolidLine); } painter.setPen(cursor.cachedColor); QRectF charRect(cursor.screenPos); charRect.setWidth(charWidth); painter.fillRect(charRect, backgroundColor); painter.drawText(charRect, Qt::AlignVCenter, cursor.cachedChar); if (cursor.isVisible) { painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter.fillRect(cursor.screenPos, QColor(0xff, 0xff, 0xff)); } } void HexWidget::drawAddrArea(QPainter &painter) { uint64_t offset = startAddress; QString addrString; QSizeF areaSize((addrCharLen + (showExAddr ? 2 : 0)) * charWidth, lineHeight); QRectF strRect(addrArea.topLeft(), areaSize); painter.setPen(addrColor); for (int line = 0; line < visibleLines && offset <= data->maxIndex(); ++line, strRect.translate(0, lineHeight), offset += itemRowByteLen()) { addrString = QString("%1").arg(offset, addrCharLen, 16, QLatin1Char('0')); if (showExAddr) addrString.prepend(hexPrefix); painter.drawText(strRect, Qt::AlignVCenter, addrString); } painter.setPen(borderColor); qreal vLineOffset = itemArea.left() - charWidth; painter.drawLine(QLineF(vLineOffset, 0, vLineOffset, viewport()->height())); } void HexWidget::drawItemArea(QPainter &painter) { QRectF itemRect(itemArea.topLeft(), QSizeF(itemWidth(), lineHeight)); QColor itemColor; QString itemString; fillSelectionBackground(painter); bool haveEditWord = false; QRectF editWordRect; QColor editWordColor; uint64_t itemAddr = startAddress; for (int line = 0; line < visibleLines; ++line) { itemRect.moveLeft(itemArea.left()); for (int j = 0; j < itemColumns; ++j) { for (int k = 0; k < itemGroupSize && itemAddr <= data->maxIndex(); ++k, itemAddr += itemByteLen) { itemString = renderItem(itemAddr - startAddress, &itemColor); if (!getFlagsAndComment(itemAddr).isEmpty()) { QColor markerColor(borderColor); markerColor.setAlphaF(0.5); painter.setPen(markerColor); for (const auto &shape : rangePolygons(itemAddr, itemAddr, false)) { painter.drawPolyline(shape); } } if (selection.contains(itemAddr) && !cursorOnAscii) { itemColor = palette().highlightedText().color(); } if (isItemDifferentAt(itemAddr)) { itemColor.setRgb(diffColor.rgb()); } if (editWordState <= EditWordState::WriteNotStarted || cursor.address != itemAddr) { painter.setPen(itemColor); painter.drawText(itemRect, Qt::AlignVCenter, itemString); itemRect.translate(itemWidth(), 0); if (cursor.address == itemAddr) { auto &itemCursor = cursorOnAscii ? shadowCursor : cursor; int itemCharPos = 0; if (editWordState > EditWordState::Read) { itemCharPos += itemPrefixLen; } if (itemCharPos < itemString.length()) { itemCursor.cachedChar = itemString.at(itemCharPos); } else { itemCursor.cachedChar = ' '; } itemCursor.cachedColor = itemColor; } } else { haveEditWord = true; editWordRect = itemRect; editWordColor = itemColor; auto &itemCursor = cursor; itemCursor.cachedChar = editWordPos < editWord.length() ? editWord[editWordPos] : QChar(' '); itemCursor.cachedColor = itemColor; itemCursor.screenPos.moveTopLeft(itemRect.topLeft()); itemCursor.screenPos.translate(charWidth * (editWordPos + itemPrefixLen), 0); itemRect.translate(itemWidth(), 0); } } itemRect.translate(columnSpacingWidth(), 0); } itemRect.translate(0, lineHeight); } if (haveEditWord) { auto length = std::max(itemCharLen, editWord.length()); auto rect = editWordRect; rect.setWidth(length * charWidth); painter.fillRect(rect, backgroundColor); painter.setPen(editWordColor); editWordRect.setWidth(4000); painter.drawText(editWordRect, Qt::AlignVCenter | Qt::AlignLeft, itemPrefix + editWord); } painter.setPen(borderColor); qreal vLineOffset = asciiArea.left() - charWidth; painter.drawLine(QLineF(vLineOffset, 0, vLineOffset, viewport()->height())); } void HexWidget::drawAsciiArea(QPainter &painter) { QRectF charRect(asciiArea.topLeft(), QSizeF(charWidth, lineHeight)); fillSelectionBackground(painter, true); uint64_t address = startAddress; QChar ascii; QColor color; for (int line = 0; line < visibleLines; ++line, charRect.translate(0, lineHeight)) { charRect.moveLeft(asciiArea.left()); for (int j = 0; j < itemRowByteLen() && address <= data->maxIndex(); ++j, ++address) { ascii = renderAscii(address - startAddress, &color); if (selection.contains(address) && cursorOnAscii) { color = palette().highlightedText().color(); } if (isItemDifferentAt(address)) { color.setRgb(diffColor.rgb()); } painter.setPen(color); /* Dots look ugly. Use fillRect() instead of drawText(). */ if (ascii == '.') { qreal a = cursor.screenPos.width(); QPointF p = charRect.bottomLeft(); p.rx() += (charWidth - a) / 2 + 1; p.ry() += -2 * a; painter.fillRect(QRectF(p, QSizeF(a, a)), color); } else { painter.drawText(charRect, Qt::AlignVCenter, ascii); } charRect.translate(charWidth, 0); if (cursor.address == address) { auto &itemCursor = cursorOnAscii ? cursor : shadowCursor; itemCursor.cachedChar = ascii; itemCursor.cachedColor = color; } } } } void HexWidget::fillSelectionBackground(QPainter &painter, bool ascii) { if (selection.isEmpty()) { return; } const auto parts = rangePolygons(selection.start(), selection.end(), ascii); for (const auto &shape : parts) { QColor highlightColor = palette().color(QPalette::Highlight); if (ascii == cursorOnAscii) { painter.setBrush(highlightColor); painter.drawPolygon(shape); } else { painter.setPen(highlightColor); painter.drawPolyline(shape); } } } QVector HexWidget::rangePolygons(RVA start, RVA last, bool ascii) { if (last < startAddress || start > lastVisibleAddr()) { return {}; } QRectF rect; const QRectF area = QRectF(ascii ? asciiArea : itemArea); /* Convert absolute values to relative */ int startOffset = std::max(uint64_t(start), startAddress) - startAddress; int endOffset = std::min(uint64_t(last), lastVisibleAddr()) - startAddress; QVector parts; auto getRectangle = [&](int offset) { return QRectF(ascii ? asciiRectangle(offset) : itemRectangle(offset)); }; auto startRect = getRectangle(startOffset); auto endRect = getRectangle(endOffset); bool startJagged = false; bool endJagged = false; if (!ascii) { if (int startFraction = startOffset % itemByteLen) { startRect.setLeft(startRect.left() + startFraction * startRect.width() / itemByteLen); startJagged = true; } if (int endFraction = itemByteLen - 1 - (endOffset % itemByteLen)) { endRect.setRight(endRect.right() - endFraction * endRect.width() / itemByteLen); endJagged = true; } } if (endOffset - startOffset + 1 <= rowSizeBytes) { if (startOffset / rowSizeBytes == endOffset / rowSizeBytes) { // single row rect = startRect; rect.setRight(endRect.right()); parts.push_back(QPolygonF(rect)); } else { // two separate rectangles rect = startRect; rect.setRight(area.right()); parts.push_back(QPolygonF(rect)); rect = endRect; rect.setLeft(area.left()); parts.push_back(QPolygonF(rect)); } } else { // single multiline shape QPolygonF shape; shape << startRect.topLeft(); rect = getRectangle(startOffset + rowSizeBytes - 1 - startOffset % rowSizeBytes); shape << rect.topRight(); if (endOffset % rowSizeBytes != rowSizeBytes - 1) { rect = getRectangle(endOffset - endOffset % rowSizeBytes - 1); shape << rect.bottomRight() << endRect.topRight(); } shape << endRect.bottomRight(); shape << getRectangle(endOffset - endOffset % rowSizeBytes).bottomLeft(); if (startOffset % rowSizeBytes) { rect = getRectangle(startOffset - startOffset % rowSizeBytes + rowSizeBytes); shape << rect.topLeft() << startRect.bottomLeft(); } shape << shape.first(); // close the shape parts.push_back(shape); } if (!ascii && (startJagged || endJagged) && parts.length() >= 1) { QPolygonF top; top.reserve(3); top << QPointF(0, 0) << QPointF(charWidth, lineHeight / 3) << QPointF(0, lineHeight / 2); QPolygonF bottom; bottom.reserve(3); bottom << QPointF(0, lineHeight / 2) << QPointF(-charWidth, 2 * lineHeight / 3) << QPointF(0, lineHeight); // small adjustment to make sure that edges don't overlap with rect edges, QPolygonF doesn't // handle it properly QPointF adjustment(charWidth / 16, 0); top.translate(-adjustment); bottom.translate(adjustment); if (startJagged) { auto movedTop = top.translated(startRect.topLeft()); auto movedBottom = bottom.translated(startRect.topLeft()); parts[0] = parts[0].subtracted(movedTop).united(movedBottom); } if (endJagged) { auto movedTop = top.translated(endRect.topRight()); auto movedBottom = bottom.translated(endRect.topRight()); parts.last() = parts.last().subtracted(movedBottom).united(movedTop); } } return parts; } void HexWidget::updateMetrics() { QFontMetricsF fontMetrics(this->monospaceFont); lineHeight = fontMetrics.height(); #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) charWidth = fontMetrics.width('A'); #else charWidth = fontMetrics.horizontalAdvance('A'); #endif updateCounts(); updateAreasHeight(); qreal cursorWidth = std::max(charWidth / 3, 1.); cursor.screenPos.setHeight(lineHeight); shadowCursor.screenPos.setHeight(lineHeight); cursor.screenPos.setWidth(cursorWidth); if (cursorOnAscii) { cursor.screenPos.moveTopLeft(asciiArea.topLeft()); shadowCursor.screenPos.setWidth(itemWidth()); shadowCursor.screenPos.moveTopLeft(itemArea.topLeft()); } else { cursor.screenPos.moveTopLeft(itemArea.topLeft()); shadowCursor.screenPos.setWidth(charWidth); shadowCursor.screenPos.moveTopLeft(asciiArea.topLeft()); } } void HexWidget::updateAreasPosition() { const qreal spacingWidth = areaSpacingWidth(); qreal yOffset = showHeader ? lineHeight : 0; addrArea.setTopLeft(QPointF(0, yOffset)); addrArea.setWidth((addrCharLen + (showExAddr ? 2 : 0)) * charWidth); itemArea.setTopLeft(QPointF(addrArea.right() + spacingWidth, yOffset)); itemArea.setWidth(itemRowWidth()); asciiArea.setTopLeft(QPointF(itemArea.right() + spacingWidth, yOffset)); asciiArea.setWidth(asciiRowWidth()); updateWidth(); } void HexWidget::updateAreasHeight() { visibleLines = static_cast((viewport()->height() - itemArea.top()) / lineHeight); qreal height = visibleLines * lineHeight; addrArea.setHeight(height); itemArea.setHeight(height); asciiArea.setHeight(height); } bool HexWidget::moveCursor(int offset, bool select, OverflowMove overflowMove) { BasicCursor addr(cursor.address); if (overflowMove == OverflowMove::Ignore) { if (addr.moveChecked(offset)) { if (addr.address > data->maxIndex()) { addr.address = data->maxIndex(); addr.pastEnd = true; } setCursorAddr(addr, select); return true; } return false; } else { addr += offset; if (addr.address > data->maxIndex()) { addr.address = data->maxIndex(); } setCursorAddr(addr, select); return true; } } void HexWidget::moveCursorKeepEditOffset(int byteOffset, bool select, OverflowMove overflowMove) { int wordOffset = editWordPos; moveCursor(byteOffset, select, overflowMove); // preserve position within word when moving vertically in hex or oct modes if (!cursorOnAscii && !select && wordOffset > 0 && navigationMode == HexNavigationMode::AnyChar && editWordState > EditWordState::Read) { startEditWord(); editWordPos = wordOffset; } } void HexWidget::setCursorAddr(BasicCursor addr, bool select) { finishEditingWord(); if (!select) { bool clearingSelection = !selection.isEmpty(); selection.init(addr); if (clearingSelection) emit selectionChanged(getSelection()); } emit positionChanged(addr.address); cursor.address = addr.address; if (!cursorOnAscii) { cursor.address -= cursor.address % itemByteLen; } /* Pause cursor repainting */ cursorEnabled = false; if (select) { selection.update(addr); emit selectionChanged(getSelection()); } uint64_t addressValue = cursor.address; /* Update data cache if necessary */ if (!(addressValue >= startAddress && addressValue <= lastVisibleAddr())) { /* Align start address */ addressValue -= (addressValue % itemRowByteLen()); /* FIXME: handling Page Up/Down */ uint64_t rowAfterVisibleAddress = startAddress + bytesPerScreen(); if (addressValue == rowAfterVisibleAddress && addressValue > startAddress) { // when pressing down add only one new row startAddress += itemRowByteLen(); } else { startAddress = addressValue; } fetchData(); if (startAddress > (data->maxIndex() - bytesPerScreen()) + 1) { startAddress = (data->maxIndex() - bytesPerScreen()) + 1; } } updateCursorMeta(); /* Draw cursor */ cursor.isVisible = !select; viewport()->update(); /* Resume cursor repainting */ cursorEnabled = selection.isEmpty(); } void HexWidget::updateCursorMeta() { QPointF point; QPointF pointAscii; int offset = cursor.address - startAddress; int itemOffset = offset; int asciiOffset; /* Calc common Y coordinate */ point.ry() = (itemOffset / itemRowByteLen()) * lineHeight; pointAscii.setY(point.y()); itemOffset %= itemRowByteLen(); asciiOffset = itemOffset; /* Calc X coordinate on the item area */ point.rx() = (itemOffset / itemGroupByteLen()) * columnExWidth(); itemOffset %= itemGroupByteLen(); point.rx() += (itemOffset / itemByteLen) * itemWidth(); /* Calc X coordinate on the ascii area */ pointAscii.rx() = asciiOffset * charWidth; point += itemArea.topLeft(); pointAscii += asciiArea.topLeft(); if (editWordState > EditWordState::Read && !cursorOnAscii) { point.rx() += itemPrefixLen * charWidth; } cursor.screenPos.moveTopLeft(cursorOnAscii ? pointAscii : point); shadowCursor.screenPos.moveTopLeft(cursorOnAscii ? point : pointAscii); } void HexWidget::setCursorOnAscii(bool ascii) { cursorOnAscii = ascii; } QColor HexWidget::itemColor(uint8_t byte) { QColor color(defColor); if (byte == 0x00) color = b0x00Color; else if (byte == 0x7f) color = b0x7fColor; else if (byte == 0xff) color = b0xffColor; else if (IS_PRINTABLE(byte)) { color = printableColor; } return color; } template static T fromBigEndian(const void *src) { #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) return qFromBigEndian(src); #else T result; memcpy(&result, src, sizeof(T)); return qFromBigEndian(result); #endif } template static T fromLittleEndian(const void *src) { #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) return qFromLittleEndian(src); #else T result; memcpy(&result, src, sizeof(T)); return qFromLittleEndian(result); #endif } QVariant HexWidget::readItem(int offset, QColor *color) { quint8 byte; quint16 word; quint32 dword; quint64 qword; float float32; double float64; quint8 bytes[sizeof(uint64_t)]; data->copy(bytes, startAddress + offset, static_cast(itemByteLen)); const bool signedItem = itemFormat == ItemFormatSignedDec; if (color) { *color = defColor; } switch (itemByteLen) { case 1: byte = bytes[0]; if (color) *color = itemColor(byte); if (!signedItem) return QVariant(static_cast(byte)); return QVariant(static_cast(static_cast(byte))); case 2: if (itemBigEndian) word = fromBigEndian(bytes); else word = fromLittleEndian(bytes); if (!signedItem) return QVariant(static_cast(word)); return QVariant(static_cast(static_cast(word))); case 4: if (itemBigEndian) dword = fromBigEndian(bytes); else dword = fromLittleEndian(bytes); if (itemFormat == ItemFormatFloat) { memcpy(&float32, &dword, sizeof(float32)); return QVariant(float32); } if (!signedItem) return QVariant(static_cast(dword)); return QVariant(static_cast(static_cast(dword))); case 8: if (itemBigEndian) qword = fromBigEndian(bytes); else qword = fromLittleEndian(bytes); if (itemFormat == ItemFormatFloat) { memcpy(&float64, &qword, sizeof(float64)); return QVariant(float64); } if (!signedItem) return QVariant(qword); return QVariant(static_cast(qword)); } return QVariant(); } QString HexWidget::renderItem(int offset, QColor *color) { QString item; QVariant itemVal = readItem(offset, color); int itemLen = itemCharLen - itemPrefixLen; /* Reserve space for prefix */ // FIXME: handle broken itemVal ( QVariant() ) switch (itemFormat) { case ItemFormatHex: item = QString("%1").arg(itemVal.toULongLong(), itemLen, 16, QLatin1Char('0')); if (itemByteLen > 1 && showExHex) item.prepend(hexPrefix); break; case ItemFormatOct: item = QString("%1").arg(itemVal.toULongLong(), itemLen, 8, QLatin1Char('0')); break; case ItemFormatDec: item = QString("%1").arg(itemVal.toULongLong(), itemLen, 10); break; case ItemFormatSignedDec: item = QString("%1").arg(itemVal.toLongLong(), itemLen, 10); break; case ItemFormatFloat: item = QString("%1").arg(itemVal.toDouble(), itemLen, 'g', itemByteLen == 4 ? 6 : 15); break; } return item; } QChar HexWidget::renderAscii(int offset, QColor *color) { uchar byte; data->copy(&byte, startAddress + offset, sizeof(byte)); if (color) { *color = itemColor(byte); } if (!IS_PRINTABLE(byte)) { byte = '.'; } return QChar(byte); } /** * @brief Gets the available flags and comment at a specific address. * @param address Address of Item to be checked. * @return String containing the flags and comment available at the address. */ QString HexWidget::getFlagsAndComment(uint64_t address) { QString flagNames = Core()->listFlagsAsStringAt(address); QString metaData = flagNames.isEmpty() ? "" : "Flags: " + flagNames.trimmed(); QString comment = Core()->getCommentAt(address); if (!comment.isEmpty()) { if (!metaData.isEmpty()) { metaData.append("\n"); } metaData.append("Comment: " + comment.trimmed()); } return metaData; } bool HexWidget::canKeyboardEdit() { return ioModesController.canWrite() && actionKeyboardEdit->isChecked(); } template static bool checkRange(BigValue v) { return v >= std::numeric_limits::min() && v <= std::numeric_limits::max(); } template static bool checkAndWrite(BigInteger value, uint8_t *buf, bool littleEndian) { if (!checkRange(value)) { return false; } if (littleEndian) { qToLittleEndian((T)value, buf); } else { qToBigEndian((T)value, buf); } return true; } template static bool checkAndWriteWithSign(const QVariant &value, uint8_t *buf, bool isSigned, bool littleEndian) { if (isSigned) { return checkAndWrite(value.toLongLong(), buf, littleEndian); } else { return checkAndWrite(value.toULongLong(), buf, littleEndian); } } bool HexWidget::parseWord(QString word, uint8_t *buf, size_t bufferSize) const { bool parseOk = false; if (bufferSize < size_t(itemByteLen)) { return false; } if (itemFormat == ItemFormatFloat) { if (itemByteLen == 4) { float value = word.toFloat(&parseOk); if (!parseOk) { return false; } if (itemBigEndian) { rz_write_be_float(buf, value); } else { rz_write_le_float(buf, value); } return true; } else if (itemByteLen == 8) { double value = word.toDouble(&parseOk); if (!parseOk) { return false; } if (itemBigEndian) { rz_write_be_double(buf, value); } else { rz_write_le_double(buf, value); } return true; } return false; } else { QVariant value; bool isSigned = false; switch (itemFormat) { case ItemFormatHex: value = word.toULongLong(&parseOk, 16); break; case ItemFormatOct: value = word.toULongLong(&parseOk, 8); break; case ItemFormatDec: value = word.toULongLong(&parseOk, 10); break; case ItemFormatSignedDec: isSigned = true; value = word.toLongLong(&parseOk, 10); break; default: break; } if (!parseOk) { return false; } switch (itemByteLen) { case 1: return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); case 2: return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); case 4: return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); case 8: return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); } } return false; } bool HexWidget::flushCurrentlyEditedWord() { if (editWordState < EditWordState::WriteEdited) { return true; } uint8_t buf[16]; if (parseWord(editWord, buf, sizeof(buf))) { data->write(buf, cursor.address, itemByteLen); return true; } editWordState = EditWordState::WriteNotEdited; return false; } bool HexWidget::finishEditingWord(bool force) { if (editWordState == EditWordState::WriteEdited) { if (!flushCurrentlyEditedWord() && !force) { qWarning() << "Not a valid number in current format or size" << editWord; showWarningRect(itemRectangle(cursor.address - startAddress).adjusted(-1, -1, 1, 1)); return false; } } editWord.clear(); editWordPos = 0; editWordState = canKeyboardEdit() ? EditWordState::WriteNotStarted : EditWordState::Read; navigationMode = defaultNavigationMode(); return true; } void HexWidget::cancelEditedWord() { editWordPos = 0; editWordState = canKeyboardEdit() ? EditWordState::WriteNotStarted : EditWordState::Read; editWord.clear(); navigationMode = defaultNavigationMode(); updateCursorMeta(); viewport()->update(); } void HexWidget::maybeFlushCharEdit() { if (editWordState < EditWordState::WriteEdited) { return; } if ((itemFormat == ItemFormatHex && earlyEditFlush >= EarlyEditFlush::EditNibble) || (isFixedWidth() && earlyEditFlush >= EarlyEditFlush::EditFixedWidthChar)) { flushCurrentlyEditedWord(); if (!flushCurrentlyEditedWord()) { showWarningRect(itemRectangle(cursor.address - startAddress).adjusted(-1, -1, 1, 1)); } } viewport()->update(); } void HexWidget::startEditWord() { if (!canKeyboardEdit()) { return; } if (editWordState >= EditWordState::WriteNotEdited) { return; } editWordPos = 0; editWordState = EditWordState::WriteNotEdited; navigationMode = defaultNavigationMode(); editWord = renderItem(cursor.address - startAddress).trimmed(); if (itemPrefixLen > 0) { editWord = editWord.mid(itemPrefixLen); } viewport()->update(); } void HexWidget::fetchData() { data.swap(oldData); data->fetch(startAddress, bytesPerScreen()); } BasicCursor HexWidget::screenPosToAddr(const QPoint &point, bool middle, int *wordOffset) const { QPointF pt = point - itemArea.topLeft(); int relativeAddress = 0; int line = static_cast(pt.y() / lineHeight); relativeAddress += line * itemRowByteLen(); int column = static_cast(pt.x() / columnExWidth()); relativeAddress += column * itemGroupByteLen(); pt.rx() -= column * columnExWidth(); auto roundingOffset = middle ? itemWidth() / 2 : 0; int posInGroup = static_cast((pt.x() + roundingOffset) / itemWidth()); if (!middle) { posInGroup = std::min(posInGroup, itemGroupSize - 1); } relativeAddress += posInGroup * itemByteLen; pt.rx() -= posInGroup * itemWidth(); BasicCursor result(startAddress); result += relativeAddress; if (!middle && wordOffset != nullptr) { int charPos = static_cast((pt.x() / charWidth) + 0.5); charPos -= itemPrefixLen; charPos = std::max(0, charPos); *wordOffset = charPos; } return result; } BasicCursor HexWidget::asciiPosToAddr(const QPoint &point, bool middle) const { QPointF pt = point - asciiArea.topLeft(); int relativeAddress = 0; relativeAddress += static_cast(pt.y() / lineHeight) * itemRowByteLen(); auto roundingOffset = middle ? (charWidth / 2) : 0; relativeAddress += static_cast((pt.x() + (roundingOffset)) / charWidth); BasicCursor result(startAddress); result += relativeAddress; return result; } BasicCursor HexWidget::currentAreaPosToAddr(const QPoint &point, bool middle) const { return cursorOnAscii ? asciiPosToAddr(point, middle) : screenPosToAddr(point, middle); } BasicCursor HexWidget::mousePosToAddr(const QPoint &point, bool middle) const { return asciiArea.contains(point) ? asciiPosToAddr(point, middle) : screenPosToAddr(point, middle); } QRectF HexWidget::itemRectangle(int offset) { qreal x; qreal y; qreal width = itemWidth(); y = (offset / itemRowByteLen()) * lineHeight; offset %= itemRowByteLen(); x = (offset / itemGroupByteLen()) * columnExWidth(); offset %= itemGroupByteLen(); x += (offset / itemByteLen) * itemWidth(); if (offset == 0) { x -= charWidth / 2; width += charWidth / 2; } if (static_cast(offset) == itemGroupByteLen() - 1) { width += charWidth / 2; } x += itemArea.x(); y += itemArea.y(); return QRectF(x, y, width, lineHeight); } QRectF HexWidget::asciiRectangle(int offset) { QPointF p; p.ry() = (offset / itemRowByteLen()) * lineHeight; offset %= itemRowByteLen(); p.rx() = offset * charWidth; p += asciiArea.topLeft(); return QRectF(p, QSizeF(charWidth, lineHeight)); } RVA HexWidget::getLocationAddress() { return !selection.isEmpty() ? selection.start() : cursor.address; } void HexWidget::hideWarningRect() { warningRectVisible = false; viewport()->update(); } void HexWidget::showWarningRect(QRectF rect) { warningRect = rect; warningRectVisible = true; warningTimer.start(WARNING_TIME_MS); viewport()->update(); }