New Hexedit (#1516)

This commit is contained in:
karliss 2019-05-16 19:03:48 +03:00 committed by Florian Märkl
parent 4f71526100
commit d55ae67dc0
12 changed files with 1709 additions and 1598 deletions

View File

@ -272,7 +272,6 @@ SOURCES += \
menus/DisassemblyContextMenu.cpp \
widgets/DisassemblyWidget.cpp \
widgets/HexdumpWidget.cpp \
widgets/HexTextView.cpp \
common/Configuration.cpp \
common/Colors.cpp \
dialogs/SaveProjectDialog.cpp \
@ -349,7 +348,8 @@ SOURCES += \
common/CrashHandler.cpp \
common/BugReporting.cpp \
common/HighDpiPixmap.cpp \
widgets/GraphGridLayout.cpp
widgets/GraphGridLayout.cpp \
widgets/HexWidget.cpp
HEADERS += \
core/Cutter.h \
@ -392,7 +392,6 @@ HEADERS += \
menus/DisassemblyContextMenu.h \
widgets/DisassemblyWidget.h \
widgets/HexdumpWidget.h \
widgets/HexTextView.h \
common/Configuration.h \
common/Colors.h \
dialogs/SaveProjectDialog.h \
@ -475,7 +474,8 @@ HEADERS += \
common/BugReporting.h \
common/HighDpiPixmap.h \
widgets/GraphLayout.h \
widgets/GraphGridLayout.h
widgets/GraphGridLayout.h \
widgets/HexWidget.h
FORMS += \
dialogs/AboutDialog.ui \
@ -501,7 +501,6 @@ FORMS += \
widgets/StringsWidget.ui \
widgets/SymbolsWidget.ui \
widgets/HexdumpWidget.ui \
widgets/HexTextView.ui \
dialogs/SaveProjectDialog.ui \
dialogs/preferences/PreferencesDialog.ui \
dialogs/preferences/AppearanceOptionsWidget.ui \

View File

@ -2785,4 +2785,22 @@ QString CutterCore::getHexdumpPreview(RVA address, int size)
return ansiEscapeToHtml(hexdump(address, size, HexdumpFormats::Normal)).replace(QLatin1Char('\n'), "<br>");
}
QByteArray CutterCore::ioRead(RVA addr, int len)
{
CORE_LOCK();
QByteArray array;
if (len <= 0)
return array;
/* Zero-copy */
array.resize(len);
if (!r_io_read_at(core_->io, addr, (uint8_t *)array.data(), len)) {
qWarning() << "Can't read data" << addr << len;
array.fill(0xff);
}
return array;
}

View File

@ -263,6 +263,8 @@ public:
void loadPDB(const QString &file);
QByteArray ioRead(RVA addr, int len);
QList<RVA> getSeekHistory();
/* Plugins */

View File

@ -65,6 +65,7 @@
#include "widgets/BacktraceWidget.h"
#include "widgets/HexdumpWidget.h"
#include "widgets/PseudocodeWidget.h"
#include "widgets/HexWidget.h"
// Qt Headers
#include <QApplication>

View File

@ -1,999 +0,0 @@
#include "HexTextView.h"
#include "ui_HexTextView.h"
#include "common/Helpers.h"
#include "common/Configuration.h"
#include "common/TempConfig.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QElapsedTimer>
#include <QTextDocumentFragment>
#include <QMenu>
#include <QClipboard>
#include <QScrollBar>
#include <QInputDialog>
#include <QShortcut>
HexTextView::HexTextView(QWidget *parent) :
QScrollArea(parent),
ui(new Ui::HexTextView)
{
ui->setupUi(this);
// Setup hex highlight
//connect(ui->hexHexText, SIGNAL(cursorPositionChanged()), this, SLOT(highlightHexCurrentLine()));
//highlightHexCurrentLine();
int margin = static_cast<int>(ui->hexOffsetText->document()->documentMargin());
ui->offsetHeaderLabel->setContentsMargins(margin, 0, margin, 0);
margin = static_cast<int>(ui->hexHexText->document()->documentMargin());
ui->hexHeaderLabel->setContentsMargins(margin, 0, margin, 0);
margin = static_cast<int>(ui->hexASCIIText->document()->documentMargin());
ui->asciiHeaderLabel->setContentsMargins(margin, 0, margin, 0);
setupFonts();
updateHeaders();
// Set hexdump context menu
ui->hexHexText->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->hexHexText, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showHexdumpContextMenu(const QPoint &)));
ui->hexASCIIText->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->hexASCIIText, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showHexASCIIContextMenu(const QPoint &)));
setupScrollSync();
// Control Disasm and Hex scroll to add more contents
connectScroll(false);
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated()));
connect(ui->hexHexText, &QTextEdit::selectionChanged, this, &HexTextView::onSelectionChanged);
connect(ui->hexASCIIText, &QTextEdit::selectionChanged, this, &HexTextView::onSelectionChanged);
connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexTextView::onSelectionChanged);
connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, this,
&HexTextView::onSelectionChanged);
connect(&rangeDialog, &QDialog::accepted, this, &HexTextView::on_rangeDialogAccepted);
addAction(ui->actionResetZoom);
connect(ui->actionResetZoom, &QAction::triggered, this, &HexTextView::zoomReset);
defaultFontSize = ui->hexHexText->font().pointSizeF();
addAction(ui->actionCopyAddressAtCursor);
ui->actionCopyAddressAtCursor->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);
format = Format::Hex;
}
void HexTextView::setupScrollSync()
{
/*
* For some reason, QScrollBar::valueChanged is not emitted when
* the scrolling happened from moving the cursor beyond the visible content,
* so QTextEdit::cursorPositionChanged has to be connected as well.
*/
auto offsetHexFunc = [this]() {
if (!scroll_disabled) {
scroll_disabled = true;
ui->hexHexText->verticalScrollBar()->setValue(ui->hexOffsetText->verticalScrollBar()->value());
scroll_disabled = false;
}
};
auto offsetASCIIFunc = [this]() {
if (!scroll_disabled) {
scroll_disabled = true;
ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexOffsetText->verticalScrollBar()->value());
scroll_disabled = false;
}
};
connect(ui->hexOffsetText->verticalScrollBar(), &QScrollBar::valueChanged,
ui->hexHexText->verticalScrollBar(), offsetHexFunc);
connect(ui->hexOffsetText, &QTextEdit::cursorPositionChanged, ui->hexHexText->verticalScrollBar(),
offsetHexFunc);
connect(ui->hexOffsetText->verticalScrollBar(), &QScrollBar::valueChanged,
ui->hexASCIIText->verticalScrollBar(), offsetASCIIFunc);
connect(ui->hexOffsetText, &QTextEdit::cursorPositionChanged, ui->hexASCIIText->verticalScrollBar(),
offsetASCIIFunc);
auto hexOffsetFunc = [this]() {
if (!scroll_disabled) {
scroll_disabled = true;
ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value());
scroll_disabled = false;
}
};
auto hexASCIIFunc = [this]() {
if (!scroll_disabled) {
scroll_disabled = true;
ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value());
scroll_disabled = false;
}
};
connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged,
ui->hexOffsetText->verticalScrollBar(), hexOffsetFunc);
connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, ui->hexOffsetText->verticalScrollBar(),
hexOffsetFunc);
connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged,
ui->hexASCIIText->verticalScrollBar(), hexASCIIFunc);
connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, ui->hexASCIIText->verticalScrollBar(),
hexASCIIFunc);
auto asciiOffsetFunc = [this]() {
if (!scroll_disabled) {
scroll_disabled = true;
ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexASCIIText->verticalScrollBar()->value());
scroll_disabled = false;
}
};
auto asciiHexFunc = [this]() {
if (!scroll_disabled) {
scroll_disabled = true;
ui->hexHexText->verticalScrollBar()->setValue(ui->hexASCIIText->verticalScrollBar()->value());
scroll_disabled = false;
}
};
connect(ui->hexASCIIText->verticalScrollBar(), &QScrollBar::valueChanged,
ui->hexOffsetText->verticalScrollBar(), asciiOffsetFunc);
connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, ui->hexOffsetText->verticalScrollBar(),
asciiOffsetFunc);
connect(ui->hexASCIIText->verticalScrollBar(), &QScrollBar::valueChanged,
ui->hexHexText->verticalScrollBar(), asciiHexFunc);
connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, ui->hexHexText->verticalScrollBar(),
asciiHexFunc);
}
void HexTextView::connectScroll(bool disconnect_)
{
scroll_disabled = disconnect_;
if (disconnect_) {
disconnect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, this,
&HexTextView::scrollChanged);
disconnect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexTextView::scrollChanged);
} else {
connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, this,
&HexTextView::scrollChanged);
connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexTextView::scrollChanged);
}
}
HexTextView::~HexTextView() {}
/*
* Text highlight functions
* Currently unused
*/
/*
void HexTextView::highlightHexCurrentLine()
{
QList<QTextEdit::ExtraSelection> extraSelections;
if (!ui->hexHexText->isReadOnly())
{
QTextEdit::ExtraSelection selection;
QColor lineColor = QColor(190, 144, 212);
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = ui->hexHexText->textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
QTextCursor cursor = ui->hexHexText->textCursor();
cursor.select(QTextCursor::WordUnderCursor);
QTextEdit::ExtraSelection currentWord;
QColor blueColor = QColor(Qt::blue).lighter(160);
currentWord.format.setBackground(blueColor);
currentWord.cursor = cursor;
extraSelections.append(currentWord);
ui->hexHexText->setExtraSelections(extraSelections);
highlightHexWords(cursor.selectedText());
}
void HexTextView::highlightHexWords(const QString &str)
{
QString searchString = str;
QTextDocument *document = ui->hexHexText->document();
document->undo();
QTextCursor highlightCursor(document);
QTextCursor cursor(document);
cursor.beginEditBlock();
QColor blueColor = QColor(Qt::blue).lighter(160);
QTextCharFormat plainFormat(highlightCursor.charFormat());
QTextCharFormat colorFormat = plainFormat;
colorFormat.setBackground(blueColor);
while (!highlightCursor.isNull() && !highlightCursor.atEnd())
{
highlightCursor = document->find(searchString, highlightCursor, QTextDocument::FindWholeWords);
if (!highlightCursor.isNull())
{
highlightCursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
highlightCursor.mergeCharFormat(colorFormat);
}
}
cursor.endEditBlock();
}
*/
void HexTextView::refresh(RVA addr)
{
if (addr == RVA_INVALID) {
addr = currentPos;
} else {
currentPos = addr;
}
ut64 loadLines = 0;
ut64 curAddrLineOffset = 0;
connectScroll(true);
updateHeaders();
cols = Core()->getConfigi("hex.cols");
// Avoid divison by 0
if (cols == 0)
cols = 16;
// TODO: Figure out how to calculate a sane value for this
bufferLines = qhelpers::getMaxFullyDisplayedLines(ui->hexHexText) * 10;
if (requestedSelectionEndAddress != 0 && requestedSelectionStartAddress != 0
&& requestedSelectionEndAddress > requestedSelectionStartAddress) {
loadLines = ((requestedSelectionEndAddress - requestedSelectionStartAddress) / cols) +
(bufferLines * 2);
curAddrLineOffset = bufferLines;
} else {
loadLines = bufferLines * 3; // total lines to load
curAddrLineOffset = bufferLines; // line number where seek should be
}
if (addr < curAddrLineOffset * cols) {
curAddrLineOffset = static_cast<int>(addr / cols);
}
if (addr > RVA_MAX - curAddrLineOffset * cols) {
curAddrLineOffset = static_cast<int>(loadLines - (RVA_MAX - addr) / cols);
}
first_loaded_address = addr - curAddrLineOffset * cols;
last_loaded_address = addr + (loadLines - curAddrLineOffset) * cols;
auto hexdump = fetchHexdump(first_loaded_address, loadLines);
ui->hexOffsetText->setText(hexdump[0]);
ui->hexHexText->setText(hexdump[1]);
ui->hexASCIIText->setPlainText(hexdump[2]);
QTextCursor cursor(ui->hexHexText->document()->findBlockByLineNumber(curAddrLineOffset));
ui->hexHexText->moveCursor(QTextCursor::End);
ui->hexHexText->ensureCursorVisible();
ui->hexHexText->setTextCursor(cursor);
ui->hexHexText->ensureCursorVisible();
// Set the backgorund color of the current seek
QTextCursor offsetCursor(ui->hexOffsetText->document()->findBlockByLineNumber(curAddrLineOffset));
QTextBlockFormat formatTmp = offsetCursor.blockFormat();
formatTmp.setBackground(QColor(64, 129, 160));
offsetCursor.setBlockFormat(formatTmp);
updateWidths();
// Update other text areas scroll
ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value());
ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value());
connectScroll(false);
}
void HexTextView::updateHeaders()
{
int cols = Core()->getConfigi("hex.cols");
int ascii_cols = cols;
bool pairs = Core()->getConfigb("hex.pairs");
QString hexHeaderString;
QString asciiHeaderString;
QTextStream hexHeader(&hexHeaderString);
QTextStream asciiHeader(&asciiHeaderString);
hexHeader.setIntegerBase(16);
hexHeader.setNumberFlags(QTextStream::UppercaseDigits);
asciiHeader.setIntegerBase(16);
asciiHeader.setNumberFlags(QTextStream::UppercaseDigits);
// Custom spacing for the header
QString space = " ";
switch (format) {
case Hex:
space = space.repeated(1);
break;
case Octal:
space = space.repeated(2);
break;
default:
qWarning() << "Unknown format in hexdump!";
break;
}
for (int i = 0; i < cols; i++) {
if (i > 0 && ((pairs && !(i & 1)) || !pairs)) {
hexHeader << " ";
}
hexHeader << space << (i & 0xF);
}
for (int i = 0; i < ascii_cols; i++) {
asciiHeader << (i & 0xF);
}
hexHeader.flush();
asciiHeader.flush();
ui->hexHeaderLabel->setText(hexHeaderString);
ui->asciiHeaderLabel->setText(asciiHeaderString);
}
std::array<QString, 3> HexTextView::fetchHexdump(RVA addr, int lines)
{
// Main bytes to fetch:
int bytes = cols * lines;
QString command = QString("pxj %1 @%2").arg(
QString::number(bytes),
RAddressString(addr));
QJsonArray byte_array = Core()->cmdj(command).array();
QString hexText = "";
QString offsetText = "";
QString asciiText = "";
RVA cur_addr = addr;
for (int i = 0; i < lines; i++) {
for (int j = 0; j < cols; j++) {
int b = byte_array[(i * cols) + j].toInt();
if ((j > 0) && (j < cols)) {
hexText += " ";
}
// Non printable
if ((b < 0x20) || (b > 0x7E)) {
asciiText += ".";
} else {
asciiText += (char)b;
}
switch (format) {
case Octal:
hexText += QString::number(b, 8).rightJustified(3, '0');
break;
case Hex:
default:
hexText += QString::number(b, 16).rightJustified(2, '0');
break;
}
}
offsetText += RAddressString(cur_addr) + "\n";
hexText += "\n";
asciiText += "\n";
cur_addr += cols;
}
return {{offsetText, hexText, asciiText}};
}
void HexTextView::onSelectionChanged()
{
if (scroll_disabled) {
return;
}
connectScroll(true);
if (sender() == ui->hexHexText) {
QTextCursor textCursor = ui->hexHexText->textCursor();
if (!textCursor.hasSelection()) {
RVA adr = hexPositionToAddress(textCursor.position());
int pos = asciiAddressToPosition(adr);
setTextEditPosition(ui->hexASCIIText, pos);
selection = {true, 0, 0};
emit selectionChanged(selection);
currentPos = adr;
emit positionChanged(adr);
connectScroll(false);
return;
}
int selectionStart = textCursor.selectionStart();
int selectionEnd = textCursor.selectionEnd();
QChar start = ui->hexHexText->document()->characterAt(selectionStart);
QChar end = ui->hexHexText->document()->characterAt(selectionEnd);
// This adjusts the selection to make sense with the chosen format
switch (format) {
case Hex:
// Handle the spaces/newlines (if it's at the start, move forward,
// if it's at the end, move back)
if (!start.isLetterOrNumber()) {
selectionStart += 1;
} else if (ui->hexHexText->document()->characterAt(selectionStart - 1).isLetterOrNumber()) {
selectionStart += 2;
}
if (!end.isLetterOrNumber()) {
selectionEnd += 1;
}
break;
case Octal:
if (!start.isLetterOrNumber()) {
selectionStart += 1;
}
if (!end.isLetterOrNumber()) {
selectionEnd += 1;
}
break;
}
// In hextext we have the spaces that we need to somehow handle.
RVA startAddress = hexPositionToAddress(selectionStart);
RVA endAddress = hexPositionToAddress(selectionEnd);
int startPosition = asciiAddressToPosition(startAddress);
int endPosition = asciiAddressToPosition(endAddress);
QTextCursor targetTextCursor = ui->hexASCIIText->textCursor();
targetTextCursor.setPosition(startPosition);
targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
ui->hexASCIIText->setTextCursor(targetTextCursor);
selection = {false, startAddress, endAddress > startAddress ? endAddress - 1 : endAddress};
emit selectionChanged(selection);
currentPos = startAddress;
emit positionChanged(startAddress);
} else {
QTextCursor textCursor = ui->hexASCIIText->textCursor();
if (!textCursor.hasSelection()) {
RVA adr = asciiPositionToAddress(textCursor.position());
int pos = hexAddressToPosition(adr);
setTextEditPosition(ui->hexHexText, pos);
connectScroll(false);
selection = {false, 0, 0};
emit selectionChanged(selection);
currentPos = adr;
emit positionChanged(adr);
return;
}
RVA startAddress = asciiPositionToAddress(textCursor.selectionStart());
RVA endAddress = asciiPositionToAddress(textCursor.selectionEnd());
int startPosition = hexAddressToPosition(startAddress);
int endPosition = hexAddressToPosition(endAddress);
// End position -1 because the position we get above is for the next
// entry, so including the space/newline
endPosition -= 1;
QTextCursor targetTextCursor = ui->hexHexText->textCursor();
targetTextCursor.setPosition(startPosition);
targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
ui->hexHexText->setTextCursor(targetTextCursor);
selection = {false, startAddress, endAddress > startAddress ? endAddress - 1 : endAddress};
emit selectionChanged(selection);
currentPos = startAddress;
emit positionChanged(startAddress);
}
connectScroll(false);
return;
}
void HexTextView::showHexdumpContextMenu(const QPoint &pt)
{
// Set Hexdump popup menu
QMenu *menu = ui->hexHexText->createStandardContextMenu();
menu->clear();
/*menu->addAction(ui->actionHexCopy_Hexpair);
menu->addAction(ui->actionHexCopy_ASCII);
menu->addAction(ui->actionHexCopy_Text);
menu->addSeparator();*/
QMenu *colSubmenu = menu->addMenu(tr("Columns"));
colSubmenu->addAction(ui->action4columns);
colSubmenu->addAction(ui->action8columns);
colSubmenu->addAction(ui->action16columns);
colSubmenu->addAction(ui->action32columns);
QMenu *formatSubmenu = menu->addMenu(tr("Format"));
formatSubmenu->addAction(ui->actionFormatHex);
formatSubmenu->addAction(ui->actionFormatOctal);
menu->addAction(ui->actionSelect_Block);
menu->addSeparator();
menu->addActions(this->actions());
// TODO:
// formatSubmenu->addAction(ui->actionFormatHalfWord);
// formatSubmenu->addAction(ui->actionFormatWord);
// formatSubmenu->addAction(ui->actionFormatQuadWord);
// formatSubmenu->addAction(ui->actionFormatEmoji);
// TODO:
// QMenu *signedIntFormatSubmenu = formatSubmenu->addMenu(tr("Signed integer"));
// signedIntFormatSubmenu->addAction(ui->actionFormatSignedInt1);
// signedIntFormatSubmenu->addAction(ui->actionFormatSignedInt2);
// signedIntFormatSubmenu->addAction(ui->actionFormatSignedInt4);
/*menu->addSeparator();
menu->addAction(ui->actionHexEdit);
menu->addAction(ui->actionHexPaste);
menu->addSeparator();
menu->addAction(ui->actionHexInsert_Hex);
menu->addAction(ui->actionHexInsert_String);*/
ui->hexHexText->setContextMenuPolicy(Qt::CustomContextMenu);
menu->exec(ui->hexHexText->mapToGlobal(pt));
delete menu;
}
void HexTextView::showHexASCIIContextMenu(const QPoint &pt)
{
// Set Hex ASCII popup menu
QMenu *menu = ui->hexASCIIText->createStandardContextMenu();
menu->clear();
/*menu->addAction(ui->actionHexCopy_Hexpair);
menu->addAction(ui->actionHexCopy_ASCII);
menu->addAction(ui->actionHexCopy_Text);
menu->addSeparator();*/
QMenu *colSubmenu = menu->addMenu("Columns");
colSubmenu->addAction(ui->action4columns);
colSubmenu->addAction(ui->action8columns);
colSubmenu->addAction(ui->action16columns);
colSubmenu->addAction(ui->action32columns);
/*menu->addSeparator();
menu->addAction(ui->actionHexEdit);
menu->addAction(ui->actionHexPaste);
menu->addSeparator();
menu->addAction(ui->actionHexInsert_Hex);
menu->addAction(ui->actionHexInsert_String);*/
ui->hexASCIIText->setContextMenuPolicy(Qt::CustomContextMenu);
menu->exec(ui->hexASCIIText->mapToGlobal(pt));
delete menu;
}
void HexTextView::setupFonts()
{
QFont font = Config()->getFont();
ui->hexOffsetText->setFont(font);
ui->hexHexText->setFont(font);
ui->hexASCIIText->setFont(font);
ui->offsetHeaderLabel->setFont(font);
ui->hexHeaderLabel->setFont(font);
ui->asciiHeaderLabel->setFont(font);
}
HexTextView::Selection HexTextView::getSelection()
{
return selection;
}
RVA HexTextView::position()
{
return currentPos;
}
void HexTextView::fontsUpdated()
{
setupFonts();
}
RVA HexTextView::hexPositionToAddress(int position)
{
switch (format) {
case Octal:
return first_loaded_address + (position / 4);
case Hex:
default:
return first_loaded_address + (position / 3);
}
return RVA_INVALID;
// In hex each byte takes up 2 characters + 1 spacer (including newline as spacer)
}
RVA HexTextView::asciiPositionToAddress(int position)
{
// Each row adds one byte (because of the newline), so cols + 1 gets rid of that offset
return first_loaded_address + (position - (position / (cols + 1)));
}
int HexTextView::hexAddressToPosition(RVA address)
{
// This strictly assumes that the address is actually loaded.
switch (format) {
case Octal:
return (address - first_loaded_address) * 4;
case Hex:
default:
return (address - first_loaded_address) * 3;
}
}
int HexTextView::asciiAddressToPosition(RVA address)
{
RVA local_address = address - first_loaded_address;
int position = local_address + (local_address / cols);
return position;
}
void HexTextView::setTextEditPosition(QTextEdit *textEdit, int position)
{
QTextCursor textCursor = textEdit->textCursor();
textCursor.setPosition(position);
textEdit->setTextCursor(textCursor);
}
int HexTextView::getDisplayedLined(QTextEdit *textEdit, bool bottom)
{
//int start_pos = textEdit->cursorForPosition(QPoint(0, 0)).position();
QPoint top_right(textEdit->viewport()->x(), textEdit->viewport()->y());
QPoint bottom_right(textEdit->viewport()->width(), textEdit->viewport()->height() - 1);
QPoint point = top_right;
if (bottom) {
point = bottom_right;
}
QTextCursor textCursor = textEdit->cursorForPosition(point);
//QTextBlock textBlock = textCursor.block();
//QTextLayout *textLayout = textBlock.layout();
//const int relativePos = textCursor.position() - textBlock.position();
//int end_pos = textEdit->cursorForPosition(bottom_right).position();
return textCursor.blockNumber();
}
void HexTextView::removeTopLinesWithoutScroll(QTextEdit *textEdit, int lines)
{
int scroll_val_before = textEdit->verticalScrollBar()->value();
int height_before = textEdit->document()->size().height();
QTextBlock block = textEdit->document()->firstBlock();
for (int i = 0; i < lines; i++) {
QTextCursor cursor(block);
block = block.next();
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
int height_after = textEdit->document()->size().height();
textEdit->verticalScrollBar()->setValue(scroll_val_before + (height_after - height_before));
}
void HexTextView::removeBottomLinesWithoutScroll(QTextEdit *textEdit, int lines)
{
QTextBlock block = textEdit->document()->lastBlock().previous();
QTextCursor textCursor = textEdit->textCursor();
for (int i = 0; i < lines; i++) {
QTextCursor cursor(block);
block = block.previous();
cursor.select(QTextCursor::BlockUnderCursor);
cursor.removeSelectedText();
}
}
void HexTextView::prependWithoutScroll(QTextEdit *textEdit, QString text)
{
// TODO: Keep selection (already works for append)
QTextCursor textCursor = textEdit->textCursor();
int current_positon = textCursor.position();
//int scroll_max_before = textEdit->verticalScrollBar()->maximum();
int scroll_val_before = textEdit->verticalScrollBar()->value();
int height_before = textEdit->document()->size().height();
textEdit->moveCursor(QTextCursor::Start);
textEdit->insertPlainText(text);
textCursor.setPosition(text.length() + current_positon);
textEdit->setTextCursor(textCursor);
int height_after = textEdit->document()->size().height();
//int scroll_max_after = textEdit->verticalScrollBar()->maximum();
//int scroll_val_after = textEdit->verticalScrollBar()->maximum();
textEdit->verticalScrollBar()->setValue(scroll_val_before + (height_after - height_before));
}
void HexTextView::appendWithoutScroll(QTextEdit *textEdit, QString text)
{
int scroll_val_before = textEdit->verticalScrollBar()->value();
QTextCursor textCursor = textEdit->textCursor();
textEdit->moveCursor(QTextCursor::End);
textEdit->insertPlainText(text);
textEdit->setTextCursor(textCursor);
textEdit->verticalScrollBar()->setValue(scroll_val_before);
}
void HexTextView::scrollChanged()
{
connectScroll(true);
int firstLine = getDisplayedLined(ui->hexHexText);
if (firstLine < (bufferLines / 2)) {
int loadLines = bufferLines;
RVA shift = static_cast<RVA>(loadLines * cols);
if (shift > first_loaded_address) {
loadLines = static_cast<int>(first_loaded_address / cols);
shift = first_loaded_address;
}
first_loaded_address -= shift;
last_loaded_address -= shift;
if (loadLines > 0) {
auto hexdump = fetchHexdump(first_loaded_address, loadLines);
prependWithoutScroll(ui->hexOffsetText, hexdump[0]);
prependWithoutScroll(ui->hexHexText, hexdump[1]);
prependWithoutScroll(ui->hexASCIIText, hexdump[2]);
removeBottomLinesWithoutScroll(ui->hexOffsetText, loadLines);
removeBottomLinesWithoutScroll(ui->hexHexText, loadLines);
removeBottomLinesWithoutScroll(ui->hexASCIIText, loadLines);
ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value());
ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value());
}
}
int blocks = ui->hexHexText->document()->blockCount();
int lastLine = getDisplayedLined(ui->hexHexText, true);
if (blocks - lastLine < (bufferLines / 2)) {
int loadLines = bufferLines;
RVA shift = static_cast<RVA>(loadLines * cols);
if (last_loaded_address > RVA_MAX - shift) {
shift = RVA_MAX - last_loaded_address;
loadLines = static_cast<int>(shift / cols);
}
if (loadLines > 0) {
auto hexdump = fetchHexdump(last_loaded_address, loadLines);
last_loaded_address += shift;
first_loaded_address += shift;
removeTopLinesWithoutScroll(ui->hexOffsetText, loadLines);
removeTopLinesWithoutScroll(ui->hexHexText, loadLines);
removeTopLinesWithoutScroll(ui->hexASCIIText, loadLines);
appendWithoutScroll(ui->hexOffsetText, hexdump[0]);
appendWithoutScroll(ui->hexHexText, hexdump[1]);
appendWithoutScroll(ui->hexASCIIText, hexdump[2]);
}
}
connectScroll(false);
}
/*
* Actions callback functions
*/
void HexTextView::on_actionCopyAddressAtCursor_triggered()
{
auto addr = hexPositionToAddress(ui->hexHexText->textCursor().position());
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(RAddressString(addr));
}
void HexTextView::on_action8columns_triggered()
{
Core()->setConfig("hex.cols", 8);
refresh();
}
void HexTextView::on_action16columns_triggered()
{
Core()->setConfig("hex.cols", 16);
refresh();
}
void HexTextView::on_action4columns_triggered()
{
Core()->setConfig("hex.cols", 4);
refresh();
}
void HexTextView::on_action32columns_triggered()
{
Core()->setConfig("hex.cols", 32);
refresh();
}
void HexTextView::on_action64columns_triggered()
{
Core()->setConfig("hex.cols", 64);
refresh();
}
void HexTextView::on_action2columns_triggered()
{
Core()->setConfig("hex.cols", 2);
refresh();
}
void HexTextView::on_action1column_triggered()
{
Core()->setConfig("hex.cols", 1);
refresh();
}
void HexTextView::on_actionFormatHex_triggered()
{
format = Format::Hex;
refresh();
}
void HexTextView::on_actionFormatOctal_triggered()
{
format = Format::Octal;
refresh();
}
void HexTextView::on_actionSelect_Block_triggered()
{
rangeDialog.open(hexPositionToAddress(ui->hexHexText->textCursor().position()));
}
void HexTextView::resizeEvent(QResizeEvent *event)
{
QScrollArea::resizeEvent(event);
refresh();
}
void HexTextView::wheelEvent(QWheelEvent *event)
{
if ( Qt::ControlModifier == event->modifiers() ) {
const QPoint numDegrees = event->angleDelta() / 8;
if (!numDegrees.isNull()) {
const QPoint numSteps = numDegrees / 15;
if ( 0 != numSteps.y() ) {
zoomIn(numSteps.y() > 0 ? 1 : -1);
}
}
event->accept();
return;
}
event->ignore();
}
void HexTextView::selectRange(RVA start, RVA end)
{
int startPosition;
int endPosition;
QTextCursor targetTextCursor;
requestedSelectionStartAddress = start;
requestedSelectionEndAddress = end;
//not sure what the accepted user feedback mechanism is, output to console or a QMessageBox alert
if (requestedSelectionEndAddress < requestedSelectionStartAddress) {
Core()->message(tr("Error: Could not select range, end address is less then start address"));
return;
}
//seek to the start address and create a text cursor to highlight the desired range
refresh(requestedSelectionStartAddress);
//for large selections, won't be able to calculate the endPosition because hexAddressToPosition assumes the address is loaded?
startPosition = hexAddressToPosition(requestedSelectionStartAddress);
endPosition = hexAddressToPosition(requestedSelectionEndAddress + 1);
targetTextCursor = ui->hexHexText->textCursor();
targetTextCursor.setPosition(startPosition);
targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
ui->hexHexText->setTextCursor(targetTextCursor);
}
void HexTextView::on_rangeDialogAccepted()
{
if (rangeDialog.empty()) {
refresh(rangeDialog.getStartAddress());
return;
}
requestedSelectionStartAddress = rangeDialog.getStartAddress();
requestedSelectionEndAddress = rangeDialog.getEndAddress();
selectRange(requestedSelectionStartAddress, requestedSelectionEndAddress);
}
void HexTextView::showOffsets(bool show)
{
if (show) {
ui->hexOffsetText->show();
Core()->setConfig("asm.offset", 1);
} else {
ui->hexOffsetText->hide();
Core()->setConfig("asm.offset", 0);
}
}
void HexTextView::zoomIn(int range)
{
ui->hexHexText->zoomIn(range);
syncScale();
}
void HexTextView::zoomOut(int range)
{
zoomIn(-range);
}
void HexTextView::zoomReset()
{
QFont font(ui->hexHexText->font());
font.setPointSizeF(defaultFontSize);
ui->hexHexText->setFont(font);
syncScale();
}
void HexTextView::updateWidths()
{
// Update width
auto idealWidth = ui->hexHexText->document()->idealWidth();
ui->hexHexText->document()->setTextWidth(idealWidth);
ui->hexOffsetText->document()->adjustSize();
ui->hexOffsetText->setFixedWidth(ui->hexOffsetText->document()->size().width() + 1);
ui->hexASCIIText->document()->adjustSize();
ui->hexASCIIText->setMinimumWidth(ui->hexASCIIText->document()->size().width());
}
void HexTextView::syncScale()
{
ui->hexOffsetText->setFont(ui->hexHexText->font());
ui->hexASCIIText->setFont(ui->hexHexText->font());
ui->offsetHeaderLabel->setFont(ui->hexHexText->font());
ui->hexHeaderLabel->setFont(ui->hexHexText->font());
ui->asciiHeaderLabel->setFont(ui->hexHexText->font());
updateWidths();
}

View File

@ -1,158 +0,0 @@
#ifndef HexTextView_H
#define HexTextView_H
#include <QDebug>
#include <QTextEdit>
#include <QMouseEvent>
#include <QAction>
#include <QScrollArea>
#include <array>
#include <memory>
#include "core/Cutter.h"
#include "MemoryDockWidget.h"
#include "common/CutterSeekable.h"
#include "dialogs/HexdumpRangeDialog.h"
#include "common/Highlighter.h"
#include "common/HexAsciiHighlighter.h"
#include "common/HexHighlighter.h"
#include "common/SvgIconEngine.h"
#include "Dashboard.h"
namespace Ui {
class HexTextView;
}
class RefreshDeferrer;
class HexTextView : public QScrollArea
{
Q_OBJECT
public:
explicit HexTextView(QWidget *parent);
~HexTextView();
Highlighter *highlighter;
enum Format {
Hex,
Octal,
// TODO:
// HalfWord,
// Word,
// QuadWord,
// Emoji,
// SignedInt1,
// SignedInt2,
// SignedInt4,
};
void refresh(RVA addr = RVA_INVALID);
/**
* @brief Select non empty inclusive range [start; end]
* @param start
* @param end
*/
void selectRange(RVA start, RVA end);
void setupFonts();
struct Selection {
bool empty;
RVA startAddress;
RVA endAddress;
};
Selection getSelection();
RVA position();
public slots:
void on_rangeDialogAccepted();
void showOffsets(bool show);
void zoomIn(int range = 1);
void zoomOut(int range = 1);
void zoomReset();
signals:
void selectionChanged(Selection selection);
void positionChanged(RVA start);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
virtual void wheelEvent(QWheelEvent *event) override;
private:
enum Format format = Format::Hex;
std::unique_ptr<Ui::HexTextView> ui;
bool sent_seek = false;
bool scroll_disabled = false;
RVA first_loaded_address = RVA_INVALID;
RVA last_loaded_address = RVA_INVALID;
void updateHeaders();
std::array<QString, 3> fetchHexdump(RVA addr, int lines);
void connectScroll(bool disconnect_);
void setupScrollSync();
// If bottom = false gets the FIRST displayed line, otherwise the LAST displayed
// line.
int getDisplayedLined(QTextEdit *textEdit, bool bottom = false);
static void removeTopLinesWithoutScroll(QTextEdit *textEdit, int lines);
static void removeBottomLinesWithoutScroll(QTextEdit *textEdit, int lines);
static void prependWithoutScroll(QTextEdit *textEdit, QString text);
static void appendWithoutScroll(QTextEdit *textEdit, QString text);
static void setTextEditPosition(QTextEdit *textEdit, int position);
RVA hexPositionToAddress(int position);
RVA asciiPositionToAddress(int position);
int hexAddressToPosition(RVA address);
int asciiAddressToPosition(RVA address);
void updateWidths();
void syncScale();
void updateParseWindow(RVA start_address, int size);
void clearParseWindow();
int bufferLines = 0;
int cols = 0;
ut64 requestedSelectionStartAddress=0;
ut64 requestedSelectionEndAddress=0;
HexdumpRangeDialog rangeDialog;
RVA currentPos = 0;
qreal defaultFontSize;
Selection selection;
private slots:
// Currently unused/untested
// void highlightHexCurrentLine();
// void highlightHexWords(const QString &str);
void showHexdumpContextMenu(const QPoint &pt);
void showHexASCIIContextMenu(const QPoint &pt);
void onSelectionChanged();
void scrollChanged();
void on_actionCopyAddressAtCursor_triggered();
void on_action1column_triggered();
void on_action2columns_triggered();
void on_action4columns_triggered();
void on_action8columns_triggered();
void on_action16columns_triggered();
void on_action32columns_triggered();
void on_action64columns_triggered();
void on_actionFormatHex_triggered();
void on_actionFormatOctal_triggered();
void on_actionSelect_Block_triggered();
void fontsUpdated();
};
#endif // HexTextView_H

View File

@ -1,418 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ui version="4.0">
<class>HexTextView</class>
<widget class="QScrollArea" name="HexTextView">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>515</width>
<height>770</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0,0,0,1">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="3">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="hexHeaderLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>0 1 2 3 ...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTextEdit" name="hexOffsetText">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="asciiHeaderLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>0123...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="offsetHeaderLabel">
<property name="text">
<string>Offset</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QTextEdit" name="hexASCIIText">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Anonymous Pro</family>
<pointsize>13</pointsize>
</font>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="cursorWidth">
<number>3</number>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QTextEdit" name="hexHexText">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
<action name="actionFunctionsUndefine">
<property name="text">
<string>Undefine</string>
</property>
<property name="toolTip">
<string>Undefine</string>
</property>
</action>
<action name="actionDisasCopy_All">
<property name="text">
<string>Copy all</string>
</property>
<property name="toolTip">
<string>Copy all</string>
</property>
</action>
<action name="actionDisasCopy_Bytes">
<property name="text">
<string>Copy bytes</string>
</property>
<property name="toolTip">
<string>Copy bytes</string>
</property>
</action>
<action name="actionDisasCopy_Disasm">
<property name="text">
<string>Copy disasm</string>
</property>
<property name="toolTip">
<string>Copy disasm</string>
</property>
</action>
<action name="actionHexCopy_Hexpair">
<property name="text">
<string>Copy Hexpair</string>
</property>
<property name="toolTip">
<string>Copy Hexpair</string>
</property>
</action>
<action name="actionHexCopy_ASCII">
<property name="text">
<string>Copy ASCII</string>
</property>
<property name="toolTip">
<string>Copy ASCII</string>
</property>
</action>
<action name="actionHexCopy_Text">
<property name="text">
<string>Copy Text</string>
</property>
<property name="toolTip">
<string>Copy Text</string>
</property>
</action>
<action name="actionCopyAddressAtCursor">
<property name="text">
<string>Copy Address</string>
</property>
<property name="toolTip">
<string>Copy Address at Cursor</string>
</property>
</action>
<action name="action1column">
<property name="text">
<string>1</string>
</property>
<property name="toolTip">
<string>1</string>
</property>
</action>
<action name="action2columns">
<property name="text">
<string>2</string>
</property>
<property name="toolTip">
<string>2</string>
</property>
</action>
<action name="action4columns">
<property name="text">
<string>4</string>
</property>
<property name="toolTip">
<string>4</string>
</property>
</action>
<action name="action8columns">
<property name="text">
<string>8</string>
</property>
<property name="toolTip">
<string>8</string>
</property>
</action>
<action name="action16columns">
<property name="text">
<string>16</string>
</property>
<property name="toolTip">
<string>16</string>
</property>
</action>
<action name="action32columns">
<property name="text">
<string>32</string>
</property>
<property name="toolTip">
<string>32</string>
</property>
</action>
<action name="action64columns">
<property name="text">
<string>64</string>
</property>
<property name="toolTip">
<string>64</string>
</property>
</action>
<action name="actionHexEdit">
<property name="text">
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit</string>
</property>
</action>
<action name="actionHexPaste">
<property name="text">
<string>Paste</string>
</property>
<property name="toolTip">
<string>Paste</string>
</property>
</action>
<action name="actionHexInsert_Hex">
<property name="text">
<string>Insert Hex</string>
</property>
<property name="toolTip">
<string>Insert Hex</string>
</property>
</action>
<action name="actionHexInsert_String">
<property name="text">
<string>Insert String</string>
</property>
<property name="toolTip">
<string>Insert String</string>
</property>
</action>
<action name="actionFormatHex">
<property name="text">
<string>Hex</string>
</property>
</action>
<action name="actionFormatOctal">
<property name="text">
<string>Octal</string>
</property>
</action>
<action name="actionFormatHalfWord">
<property name="text">
<string>Half-word</string>
</property>
</action>
<action name="actionFormatWord">
<property name="text">
<string>Word</string>
</property>
</action>
<action name="actionFormatQuadWord">
<property name="text">
<string>Quad-word</string>
</property>
</action>
<action name="actionFormatEmoji">
<property name="text">
<string>Emoji</string>
</property>
</action>
<action name="actionFormatSignedInt1">
<property name="text">
<string>1 byte</string>
</property>
<property name="toolTip">
<string>1 byte</string>
</property>
</action>
<action name="actionFormatSignedInt2">
<property name="text">
<string>2 bytes</string>
</property>
<property name="toolTip">
<string>2 bytes</string>
</property>
</action>
<action name="actionFormatSignedInt4">
<property name="text">
<string>4 bytes</string>
</property>
<property name="toolTip">
<string>4 bytes</string>
</property>
</action>
<action name="actionSelect_Block">
<property name="text">
<string>Select Block...</string>
</property>
</action>
<action name="actionResetZoom">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Reset zoom</string>
</property>
<property name="shortcut">
<string>Ctrl+0</string>
</property>
<property name="shortcutContext">
<enum>Qt::WindowShortcut</enum>
</property>
</action>
</widget>
<resources />
<connections />
</ui>

1190
src/widgets/HexWidget.cpp Normal file

File diff suppressed because it is too large Load Diff

463
src/widgets/HexWidget.h Normal file
View File

@ -0,0 +1,463 @@
#ifndef HEXWIDGET_H
#define HEXWIDGET_H
#include "Cutter.h"
#include "dialogs/HexdumpRangeDialog.h"
#include <QScrollArea>
#include <QTimer>
#include <QMenu>
struct BasicCursor
{
uint64_t address;
bool pastEnd;
BasicCursor(uint64_t pos) : address(pos), pastEnd(false) {}
BasicCursor() : address(0), pastEnd(false) {}
BasicCursor &operator+=(int64_t offset)
{
if (offset < 0 && uint64_t(-offset) > address) {
address = 0;
pastEnd = false;
} else if (offset > 0 && uint64_t(offset) > (UINT64_MAX - address)) {
address = UINT64_MAX;
pastEnd = true;
} else {
address += uint64_t(offset);
pastEnd = false;
}
return *this;
}
BasicCursor &operator+=(int offset)
{
*this += int64_t(offset);
return *this;
}
BasicCursor &operator+=(uint64_t offset)
{
if (uint64_t(offset) > (UINT64_MAX - address)) {
address = UINT64_MAX;
pastEnd = true;
} else {
address += offset;
pastEnd = false;
}
return *this;
}
bool operator<(const BasicCursor &r)
{
return address < r.address || (pastEnd < r.pastEnd);
}
};
struct HexCursor
{
HexCursor() { isVisible = false; onAsciiArea = false; }
bool isVisible;
bool onAsciiArea;
QTimer blinkTimer;
QRect screenPos;
uint64_t address;
QString cachedChar;
QColor cachedColor;
void blink() { isVisible = !isVisible; }
void setBlinkPeriod(int msec) { blinkTimer.setInterval(msec / 2); }
void startBlinking() { blinkTimer.start(); }
void stopBlinking() { blinkTimer.stop(); }
};
class AbstractData
{
public:
virtual ~AbstractData() {}
virtual void fetch(uint64_t addr, int len) = 0;
virtual const void *dataPtr(uint64_t addr) = 0;
virtual uint64_t maxIndex() = 0;
};
class BufferData : public AbstractData
{
public:
BufferData()
{
m_buffer.fill(0, 1);
}
BufferData(const QByteArray &buffer)
{
if (buffer.isEmpty()) {
m_buffer.fill(0, 1);
} else {
m_buffer = buffer;
}
}
~BufferData() override {}
void fetch(uint64_t addr, int len) override {}
const void *dataPtr(uint64_t addr) override
{
return m_buffer.constData() + addr;
}
uint64_t maxIndex() override
{
return m_buffer.size() - 1;
}
private:
QByteArray m_buffer;
};
class MemoryData : public AbstractData
{
public:
MemoryData() {}
~MemoryData() override {}
void fetch(uint64_t address, int length) override
{
// FIXME: reuse data if possible
uint64_t alignedAddr = address & ~(4096ULL - 1);
int offset = address - alignedAddr;
int len = (offset + length + (4096 - 1)) & ~(4096 - 1);
m_firstBlockAddr = alignedAddr;
m_blocks.clear();
uint64_t addr = alignedAddr;
for (int i = 0; i < len / 4096; ++i, addr += 4096) {
m_blocks.append(Core()->ioRead(addr, 4096));
}
}
const void *dataPtr(uint64_t addr) override
{
int totalOffset = addr - m_firstBlockAddr;
int blockId = totalOffset / 4096;
int blockOffset = totalOffset % 4096;
return static_cast<const void *>(m_blocks.at(blockId).constData() + blockOffset);
}
virtual uint64_t maxIndex() override
{
return UINT64_MAX;
}
private:
QVector<QByteArray> m_blocks;
uint64_t m_firstBlockAddr;
};
class HexSelection
{
public:
HexSelection() { m_empty = true; }
inline void init(BasicCursor addr)
{
m_empty = true;
m_init = addr;
}
void set(uint64_t start, uint64_t end)
{
m_empty = false;
m_init = m_start = start;
m_end = end;
}
void update(BasicCursor addr)
{
m_empty = false;
if (m_init < addr) {
m_start = m_init.address;
m_end = addr.address;
if (!addr.pastEnd)
m_end -= 1;
} else if (addr < m_init) {
m_start = addr.address;
m_end = m_init.address;
if (!m_init.pastEnd)
m_end -= 1;
} else {
m_start = m_end = m_init.address;
m_empty = true;
}
}
bool intersects(uint64_t start, uint64_t end)
{
return !m_empty && m_end >= start && m_start <= end;
}
bool contains(uint64_t pos) const
{
return !m_empty && m_start <= pos && pos <= m_end;
}
uint64_t size()
{
uint64_t size = 0;
if (!isEmpty())
size = m_end - m_start + 1;
return size;
}
inline bool isEmpty() { return m_empty; }
inline uint64_t start() { return m_start; }
inline uint64_t end() { return m_end; }
private:
BasicCursor m_init;
uint64_t m_start;
uint64_t m_end;
bool m_empty;
};
class HexWidget : public QScrollArea
{
Q_OBJECT
public:
explicit HexWidget(QWidget *parent = nullptr);
~HexWidget();
void setMonospaceFont(const QFont &font);
enum AddrWidth { AddrWidth32 = 8, AddrWidth64 = 16 };
enum ItemSize { ItemSizeByte = 1, ItemSizeWord = 2, ItemSizeDword = 4, ItemSizeQword = 8 };
enum ItemFormat { ItemFormatHex, ItemFormatOct, ItemFormatDec, ItemFormatSignedDec, ItemFormatFloat };
enum class ColumnMode { Fixed, PowerOf2 };
void setItemSize(int nbytes);
void setItemFormat(ItemFormat format);
void setItemEndianess(bool bigEndian);
void setItemGroupSize(int size);
/**
* @brief Sets line size in bytes.
* Changes column mode to fixed. Command can be rejected if current item format is bigger than requested size.
* @param bytes line size in bytes.
*/
void setFixedLineSize(int bytes);
void setColumnMode(ColumnMode mode);
/**
* @brief Select non empty inclusive range [start; end]
* @param start
* @param end
*/
void selectRange(RVA start, RVA end);
void clearSelection();
struct Selection {
bool empty;
RVA startAddress;
RVA endAddress;
};
Selection getSelection();
public slots:
void seek(uint64_t address);
void refresh();
void updateColors();
signals:
void selectionChanged(Selection selection);
void positionChanged(RVA start);
protected:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
private slots:
void onCursorBlinked();
void onHexPairsModeEnabled(bool enable);
void copy();
void copyAddress();
void onRangeDialogAccepted();
private:
void updateItemLength();
void updateCounts();
void drawHeader(QPainter &painter);
void drawCursor(QPainter &painter, bool shadow = false);
void drawAddrArea(QPainter &painter);
void drawItemArea(QPainter &painter);
void drawAsciiArea(QPainter &painter);
void fillSelectionBackground(QPainter &painter, bool ascii = false);
void updateMetrics();
void updateAreasPosition();
void updateAreasHeight();
void moveCursor(int offset, bool select = false);
void setCursorAddr(BasicCursor addr, bool select = false);
void updateCursorMeta();
void setCursorOnAscii(bool ascii);
const QColor itemColor(uint8_t byte);
QVariant readItem(int offset, QColor *color = nullptr);
QString renderItem(int offset, QColor *color = nullptr);
QChar renderAscii(int offset, QColor *color = nullptr);
void fetchData();
/**
* @brief Convert mouse position to address.
* @param point mouse position in widget
* @param middle start next position from middle of symbol. Use middle=true for vertical cursror position between symbols,
* middle=false for insert mode cursor and getting symbol under cursor.
* @return
*/
BasicCursor screenPosToAddr(const QPoint &point, bool middle = false) const;
BasicCursor asciiPosToAddr(const QPoint &point, bool middle = false) const;
BasicCursor currentAreaPosToAddr(const QPoint &point, bool middle = false) const;
BasicCursor mousePosToAddr(const QPoint &point, bool middle = false) const;
QRect itemRectangle(uint offset);
QRect asciiRectangle(uint offset);
void updateWidth();
inline int itemWidth() const
{
return itemCharLen * charWidth;
}
inline int itemGroupCharLen() const
{
return itemCharLen * itemGroupSize;
}
inline int columnExCharLen() const
{
return itemGroupCharLen() + columnSpacing;
}
inline int itemGroupByteLen() const
{
return itemByteLen * itemGroupSize;
}
inline int columnWidth() const
{
return itemGroupCharLen() * charWidth;
}
inline int columnExWidth() const
{
return columnExCharLen() * charWidth;
}
inline int columnSpacingWidth() const
{
return columnSpacing * charWidth;
}
inline int itemRowCharLen() const
{
return itemColumns * columnExCharLen() - columnSpacing;
}
inline int itemRowByteLen() const
{
return rowSizeBytes;
}
inline int bytesPerScreen() const
{
return itemRowByteLen() * visibleLines;
}
inline int itemRowWidth() const
{
return itemRowCharLen() * charWidth;
}
inline int asciiRowWidth() const
{
return itemRowByteLen() * charWidth;
}
inline int areaSpacingWidth() const
{
return areaSpacing * charWidth;
}
inline uint64_t lastVisibleAddr() const
{
return (startAddress - 1) + bytesPerScreen();
}
const QRect &currentArea() const
{
return cursorOnAscii ? asciiArea : itemArea;
}
bool cursorEnabled;
bool cursorOnAscii;
HexCursor cursor;
HexCursor shadowCursor;
HexSelection selection;
bool updatingSelection;
QRect addrArea;
QRect itemArea;
QRect asciiArea;
int itemByteLen;
int itemGroupSize; ///< Items per group (default: 1), 2 in case of hexpair mode
int rowSizeBytes; ///< Line size in bytes
int itemColumns; ///< Number of columns, single column consists of itemGroupSize items
int itemCharLen;
int itemPrefixLen;
ColumnMode columnMode;
ItemFormat itemFormat;
bool itemBigEndian;
int visibleLines;
uint64_t startAddress;
int charWidth;
int byteWidth;
int lineHeight;
int addrCharLen;
int addrAreaWidth;
QFont monospaceFont;
bool showHeader;
bool showAscii;
bool showExHex;
bool showExAddr;
QColor borderColor;
QColor backgroundColor;
QColor defColor;
QColor addrColor;
QColor b0x00Color;
QColor b0x7fColor;
QColor b0xffColor;
QColor printableColor;
HexdumpRangeDialog rangeDialog;
/* Spacings in characters */
const int columnSpacing = 1;
const int areaSpacing = 2;
const QString hexPrefix = QStringLiteral("0x");
QMenu* rowSizeMenu;
QAction* actionRowSizePowerOf2;
QList<QAction *> actionsItemSize;
QList<QAction *> actionsItemFormat;
QAction *actionItemBigEndian;
QAction *actionHexPairs;
QAction *actionCopy;
QAction *actionCopyAddress;
QAction *actionSelectRange;
AbstractData *data;
};
#endif // HEXWIDGET_H

View File

@ -95,19 +95,17 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) :
}
});
connect(Core(), &CutterCore::refreshAll, this, [this]() {
refresh(seekable->getOffset());
});
connect(Core(), &CutterCore::refreshAll, this, [this]() { refresh(); });
connect(seekable, &CutterSeekable::seekableSeekChanged, this, &HexdumpWidget::onSeekChanged);
connect(ui->hexTextView, &HexTextView::positionChanged, this, [this](RVA addr) {
connect(ui->hexTextView, &HexWidget::positionChanged, this, [this](RVA addr) {
if (!sent_seek) {
sent_seek = true;
seekable->seek(addr);
sent_seek = false;
}
});
connect(ui->hexTextView, &HexTextView::selectionChanged, this, &HexdumpWidget::selectionChanged);
connect(ui->hexTextView, &HexWidget::selectionChanged, this, &HexdumpWidget::selectionChanged);
initParsing();
selectHexPreview();
@ -124,13 +122,23 @@ void HexdumpWidget::onSeekChanged(RVA addr)
HexdumpWidget::~HexdumpWidget() {}
void HexdumpWidget::refresh()
{
refresh(RVA_INVALID);
}
void HexdumpWidget::refresh(RVA addr)
{
if (!refreshDeferrer->attemptRefresh(addr == RVA_INVALID ? nullptr : new RVA(addr))) {
return;
}
sent_seek = true;
ui->hexTextView->refresh(addr);
if (addr != RVA_INVALID) {
ui->hexTextView->seek(addr);
} else {
ui->hexTextView->refresh();
refreshSelectionInfo();
}
sent_seek = false;
}
@ -143,7 +151,7 @@ void HexdumpWidget::initParsing()
ui->parseEndianComboBox->setCurrentIndex(Core()->getConfigb("cfg.bigendian") ? 1 : 0);
}
void HexdumpWidget::selectionChanged(HexTextView::Selection selection)
void HexdumpWidget::selectionChanged(HexWidget::Selection selection)
{
if (selection.empty) {
clearParseWindow();
@ -179,7 +187,7 @@ void HexdumpWidget::setupFonts()
{
QFont font = Config()->getFont();
ui->hexDisasTextEdit->setFont(font);
ui->hexTextView->setupFonts();
ui->hexTextView->setMonospaceFont(font);
}
void HexdumpWidget::refreshSelectionInfo()
@ -318,6 +326,10 @@ void HexdumpWidget::resizeEvent(QResizeEvent *event)
refresh();
}
QWidget *HexdumpWidget::widgetToFocusOnRaise()
{
return ui->hexTextView;
}
void HexdumpWidget::on_copyMD5_clicked()
{

View File

@ -16,7 +16,7 @@
#include "common/HexAsciiHighlighter.h"
#include "common/HexHighlighter.h"
#include "common/SvgIconEngine.h"
#include "HexTextView.h"
#include "HexWidget.h"
#include "Dashboard.h"
@ -31,7 +31,7 @@ class HexdumpWidget : public MemoryDockWidget
Q_OBJECT
public:
explicit HexdumpWidget(MainWindow *main, QAction *action = nullptr);
~HexdumpWidget();
~HexdumpWidget() override;
Highlighter *highlighter;
public slots:
@ -40,7 +40,7 @@ public slots:
void toggleSync();
protected:
virtual void resizeEvent(QResizeEvent *event) override;
QWidget *widgetToFocusOnRaise() override;
private:
std::unique_ptr<Ui::HexdumpWidget> ui;
@ -48,7 +48,8 @@ private:
RefreshDeferrer *refreshDeferrer;
void refresh(RVA addr = RVA_INVALID);
void refresh();
void refresh(RVA addr);
void selectHexPreview();
void setupFonts();
@ -65,7 +66,7 @@ private slots:
void on_actionHideHexdump_side_panel_triggered();
void selectionChanged(HexTextView::Selection selection);
void selectionChanged(HexWidget::Selection selection);
void on_parseArchComboBox_currentTextChanged(const QString &arg1);
void on_parseBitsComboBox_currentTextChanged(const QString &arg1);

View File

@ -32,7 +32,7 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="HexTextView" name="hexTextView">
<widget class="HexWidget" name="hexTextView">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
@ -674,9 +674,9 @@
</widget>
<customwidgets>
<customwidget>
<class>HexTextView</class>
<class>HexWidget</class>
<extends>QScrollArea</extends>
<header>HexTextView.h</header>
<header>HexWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>