2019-05-16 16:03:48 +00:00
|
|
|
#include "HexWidget.h"
|
|
|
|
#include "Cutter.h"
|
|
|
|
#include "Configuration.h"
|
2020-03-19 09:36:36 +00:00
|
|
|
#include "dialogs/WriteCommandsDialogs.h"
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QPaintEvent>
|
|
|
|
#include <QResizeEvent>
|
|
|
|
#include <QMouseEvent>
|
|
|
|
#include <QKeyEvent>
|
|
|
|
#include <QWheelEvent>
|
|
|
|
#include <QtEndian>
|
|
|
|
#include <QScrollBar>
|
|
|
|
#include <QMenu>
|
|
|
|
#include <QClipboard>
|
|
|
|
#include <QApplication>
|
2020-03-19 09:36:36 +00:00
|
|
|
#include <QInputDialog>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QRegularExpression>
|
2020-04-04 07:09:30 +00:00
|
|
|
#include <QToolTip>
|
2020-11-30 07:42:13 +00:00
|
|
|
#include <QActionGroup>
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2020-03-19 09:36:36 +00:00
|
|
|
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;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
setMouseTracking(true);
|
|
|
|
setFocusPolicy(Qt::FocusPolicy::StrongFocus);
|
|
|
|
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, [this]() { viewport()->update(); });
|
|
|
|
|
|
|
|
connect(Config(), &Configuration::colorsUpdated, this, &HexWidget::updateColors);
|
2019-10-12 05:50:10 +00:00
|
|
|
connect(Config(), &Configuration::fontsUpdated, this, [this]() { setMonospaceFont(
|
|
|
|
Config()->getFont()); });
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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<ItemFormat>(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::setItemEndianess);
|
|
|
|
|
|
|
|
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);
|
2019-05-17 16:40:45 +00:00
|
|
|
actionCopy->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut);
|
2019-05-16 16:03:48 +00:00
|
|
|
actionCopy->setShortcut(QKeySequence::Copy);
|
|
|
|
connect(actionCopy, &QAction::triggered, this, &HexWidget::copy);
|
|
|
|
|
|
|
|
actionCopyAddress = new QAction(tr("Copy address"), this);
|
2019-05-17 16:40:45 +00:00
|
|
|
actionCopyAddress->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut);
|
2019-05-16 16:03:48 +00:00
|
|
|
actionCopyAddress->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);
|
|
|
|
connect(actionCopyAddress, &QAction::triggered, this, &HexWidget::copyAddress);
|
|
|
|
addAction(actionCopyAddress);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2020-03-19 09:36:36 +00:00
|
|
|
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 De\\Encoded Base64 string"), this);
|
|
|
|
connect(actionWrite64, &QAction::triggered, this, &HexWidget::w_write64);
|
|
|
|
actionsWriteString.append(actionWrite64);
|
|
|
|
|
|
|
|
actionsWriteOther.reserve(4);
|
|
|
|
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);
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
connect(this, &HexWidget::selectionChanged, this, [this](Selection selection) {
|
|
|
|
actionCopy->setEnabled(!selection.empty);
|
|
|
|
});
|
|
|
|
|
|
|
|
updateMetrics();
|
|
|
|
updateItemLength();
|
|
|
|
|
|
|
|
startAddress = 0ULL;
|
|
|
|
cursor.address = 0ULL;
|
2019-05-26 10:12:23 +00:00
|
|
|
data.reset(new MemoryData());
|
2019-11-30 14:55:29 +00:00
|
|
|
oldData.reset(new MemoryData());
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
fetchData();
|
|
|
|
updateCursorMeta();
|
|
|
|
|
|
|
|
connect(&cursor.blinkTimer, &QTimer::timeout, this, &HexWidget::onCursorBlinked);
|
|
|
|
cursor.setBlinkPeriod(1000);
|
|
|
|
cursor.startBlinking();
|
|
|
|
|
|
|
|
updateColors();
|
|
|
|
}
|
|
|
|
|
|
|
|
HexWidget::~HexWidget()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setMonospaceFont(const QFont &font)
|
|
|
|
{
|
|
|
|
if (!(font.styleHint() & QFont::Monospace)) {
|
|
|
|
/* FIXME: Use default monospace font
|
|
|
|
setFont(XXX); */
|
|
|
|
}
|
|
|
|
QScrollArea::setFont(font);
|
2019-06-18 08:34:30 +00:00
|
|
|
monospaceFont = font.resolve(this->font());
|
2019-05-16 16:03:48 +00:00
|
|
|
updateMetrics();
|
|
|
|
fetchData();
|
|
|
|
updateCursorMeta();
|
|
|
|
|
|
|
|
viewport()->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setItemSize(int nbytes)
|
|
|
|
{
|
|
|
|
static const QVector<int> values({1, 2, 4, 8});
|
|
|
|
|
|
|
|
if (!values.contains(nbytes))
|
|
|
|
return;
|
|
|
|
|
|
|
|
itemByteLen = nbytes;
|
|
|
|
if (itemByteLen > rowSizeBytes) {
|
|
|
|
rowSizeBytes = itemByteLen;
|
|
|
|
}
|
|
|
|
|
|
|
|
actionsItemFormat.at(ItemFormatFloat)->setEnabled(nbytes >= 4);
|
|
|
|
actionItemBigEndian->setEnabled(nbytes != 1);
|
|
|
|
|
|
|
|
updateItemLength();
|
|
|
|
fetchData();
|
|
|
|
updateCursorMeta();
|
|
|
|
|
|
|
|
viewport()->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setItemFormat(ItemFormat format)
|
|
|
|
{
|
|
|
|
itemFormat = format;
|
|
|
|
|
|
|
|
bool sizeEnabled = true;
|
|
|
|
if (format == ItemFormatFloat)
|
|
|
|
sizeEnabled = false;
|
|
|
|
actionsItemSize.at(0)->setEnabled(sizeEnabled);
|
|
|
|
actionsItemSize.at(1)->setEnabled(sizeEnabled);
|
|
|
|
|
|
|
|
|
|
|
|
updateItemLength();
|
|
|
|
fetchData();
|
|
|
|
updateCursorMeta();
|
|
|
|
|
|
|
|
viewport()->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setItemGroupSize(int size)
|
|
|
|
{
|
|
|
|
itemGroupSize = size;
|
|
|
|
|
|
|
|
updateCounts();
|
|
|
|
fetchData();
|
|
|
|
updateCursorMeta();
|
|
|
|
|
|
|
|
viewport()->update();
|
|
|
|
}
|
|
|
|
|
2019-11-30 14:55:29 +00:00
|
|
|
/**
|
|
|
|
* @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) {
|
2020-04-16 17:32:24 +00:00
|
|
|
char oldItem[sizeof(uint64_t)] = {};
|
|
|
|
char newItem[sizeof(uint64_t)] = {};
|
|
|
|
if (data->copy(newItem, address, static_cast<size_t>(itemByteLen)) &&
|
|
|
|
oldData->copy(oldItem, address, static_cast<size_t>(itemByteLen))) {
|
|
|
|
return memcmp(oldItem, newItem, sizeof(oldItem)) != 0;
|
2019-11-30 14:55:29 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
void HexWidget::updateCounts()
|
|
|
|
{
|
|
|
|
actionHexPairs->setEnabled(rowSizeBytes > 1 && itemByteLen == 1
|
|
|
|
&& itemFormat == ItemFormat::ItemFormatHex);
|
2019-05-29 06:25:23 +00:00
|
|
|
actionHexPairs->setChecked(Core()->getConfigb("hex.pairs"));
|
2019-05-16 16:03:48 +00:00
|
|
|
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(cursor.address, false);
|
|
|
|
emit selectionChanged(getSelection());
|
|
|
|
}
|
|
|
|
|
|
|
|
HexWidget::Selection HexWidget::getSelection()
|
|
|
|
{
|
|
|
|
return Selection{selection.isEmpty(), selection.start(), selection.end()};
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::seek(uint64_t address)
|
|
|
|
{
|
|
|
|
setCursorAddr(address);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::refresh()
|
|
|
|
{
|
|
|
|
fetchData();
|
|
|
|
viewport()->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setItemEndianess(bool bigEndian)
|
|
|
|
{
|
|
|
|
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");
|
2019-11-30 14:55:29 +00:00
|
|
|
diffColor = Config()->getColor("graph.diff.unmatch");
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
2020-08-04 18:19:17 +00:00
|
|
|
if (event->rect() == cursor.screenPos.toAlignedRect()) {
|
2019-05-16 16:03:48 +00:00
|
|
|
/* Cursor blink */
|
|
|
|
drawCursor(painter);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
painter.fillRect(event->rect().translated(xOffset, 0), backgroundColor);
|
|
|
|
|
|
|
|
drawHeader(painter);
|
|
|
|
|
|
|
|
drawAddrArea(painter);
|
|
|
|
drawItemArea(painter);
|
|
|
|
drawAsciiArea(painter);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2020-04-04 07:09:30 +00:00
|
|
|
auto mouseAddr = mousePosToAddr(pos).address;
|
|
|
|
|
|
|
|
QString metaData = getFlagsAndComment(mouseAddr);
|
|
|
|
if (!metaData.isEmpty() && itemArea.contains(pos)) {
|
|
|
|
QToolTip::showText(event->globalPos(), metaData.replace(",", ", "), this);
|
|
|
|
} else {
|
|
|
|
QToolTip::hideText();
|
|
|
|
}
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
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);
|
|
|
|
if (selecting) {
|
|
|
|
updatingSelection = true;
|
|
|
|
setCursorOnAscii(!selectingData);
|
|
|
|
auto cursorPosition = currentAreaPosToAddr(pos, true);
|
|
|
|
setCursorAddr(cursorPosition, event->modifiers() == Qt::ShiftModifier);
|
|
|
|
viewport()->update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::mouseReleaseEvent(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
if (event->button() == Qt::LeftButton) {
|
2019-08-19 05:23:42 +00:00
|
|
|
if (selection.isEmpty()) {
|
2019-05-16 16:03:48 +00:00
|
|
|
selection.init(cursor.address);
|
2019-08-19 05:23:42 +00:00
|
|
|
cursorEnabled = true;
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
updatingSelection = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::wheelEvent(QWheelEvent *event)
|
|
|
|
{
|
2020-06-06 16:02:24 +00:00
|
|
|
// 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();
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
if (dy == 0)
|
|
|
|
return;
|
|
|
|
|
2019-05-19 20:11:09 +00:00
|
|
|
if (delta < 0 && startAddress < static_cast<uint64_t>(-delta)) {
|
2019-05-16 16:03:48 +00:00
|
|
|
startAddress = 0;
|
2019-05-19 20:11:09 +00:00
|
|
|
} else if (delta > 0 && data->maxIndex() < static_cast<uint64_t>(bytesPerScreen())) {
|
2019-05-16 16:03:48 +00:00
|
|
|
startAddress = 0;
|
|
|
|
} else {
|
|
|
|
startAddress += delta;
|
|
|
|
}
|
|
|
|
fetchData();
|
2019-11-30 14:55:29 +00:00
|
|
|
if ((data->maxIndex() - startAddress) <= static_cast<uint64_t>(bytesPerScreen() + delta - 1)) {
|
|
|
|
startAddress = (data->maxIndex() - bytesPerScreen()) + 1;
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) {
|
|
|
|
moveCursor(itemRowByteLen(), select);
|
|
|
|
} else if (moveOrSelect(QKeySequence::MoveToPreviousLine, QKeySequence::SelectPreviousLine)) {
|
|
|
|
moveCursor(-itemRowByteLen(), select);
|
|
|
|
} else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) {
|
|
|
|
moveCursor(cursorOnAscii ? 1 : itemByteLen, select);
|
|
|
|
} else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar)) {
|
|
|
|
moveCursor(cursorOnAscii ? -1 : -itemByteLen, select);
|
|
|
|
} else 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);
|
|
|
|
}
|
|
|
|
//viewport()->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::contextMenuEvent(QContextMenuEvent *event)
|
|
|
|
{
|
|
|
|
QPoint pt = event->pos();
|
2019-06-17 12:13:34 +00:00
|
|
|
bool mouseOutsideSelection = false;
|
2019-05-16 16:03:48 +00:00
|
|
|
if (event->reason() == QContextMenuEvent::Mouse) {
|
|
|
|
auto mouseAddr = mousePosToAddr(pt).address;
|
2019-06-17 12:13:34 +00:00
|
|
|
if (asciiArea.contains(pt)) {
|
|
|
|
cursorOnAscii = true;
|
|
|
|
} else if (itemArea.contains(pt)) {
|
|
|
|
cursorOnAscii = false;
|
|
|
|
}
|
|
|
|
if (selection.isEmpty()) {
|
2019-05-16 16:03:48 +00:00
|
|
|
seek(mouseAddr);
|
2019-06-17 12:13:34 +00:00
|
|
|
} else {
|
|
|
|
mouseOutsideSelection = !selection.contains(mouseAddr);
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-17 12:13:34 +00:00
|
|
|
auto disableOutsideSelectionActions = [this](bool disable) {
|
|
|
|
actionCopyAddress->setDisabled(disable);
|
|
|
|
};
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
QMenu *menu = new QMenu();
|
|
|
|
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);
|
2020-03-19 09:36:36 +00:00
|
|
|
QMenu *writeMenu = menu->addMenu(tr("Edit"));
|
|
|
|
writeMenu->addActions(actionsWriteString);
|
|
|
|
writeMenu->addSeparator();
|
|
|
|
writeMenu->addActions(actionsWriteOther);
|
2019-05-16 16:03:48 +00:00
|
|
|
menu->addSeparator();
|
|
|
|
menu->addAction(actionCopy);
|
2019-06-17 12:13:34 +00:00
|
|
|
disableOutsideSelectionActions(mouseOutsideSelection);
|
2019-05-16 16:03:48 +00:00
|
|
|
menu->addAction(actionCopyAddress);
|
|
|
|
menu->addActions(this->actions());
|
|
|
|
menu->exec(mapToGlobal(pt));
|
2019-06-17 12:13:34 +00:00
|
|
|
disableOutsideSelectionActions(false);
|
2019-05-16 16:03:48 +00:00
|
|
|
menu->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::onCursorBlinked()
|
|
|
|
{
|
|
|
|
if (!cursorEnabled)
|
|
|
|
return;
|
|
|
|
cursor.blink();
|
2020-08-04 18:19:17 +00:00
|
|
|
QRect cursorRect = cursor.screenPos.toAlignedRect();
|
2019-06-18 08:34:30 +00:00
|
|
|
viewport()->update(cursorRect.translated(-horizontalScrollBar()->value(), 0));
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::onHexPairsModeEnabled(bool enable)
|
|
|
|
{
|
2019-05-29 06:25:23 +00:00
|
|
|
// Sync configuration
|
|
|
|
Core()->setConfig("hex.pairs", enable);
|
2019-05-16 16:03:48 +00:00
|
|
|
if (enable) {
|
|
|
|
setItemGroupSize(2);
|
|
|
|
} else {
|
|
|
|
setItemGroupSize(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::copy()
|
|
|
|
{
|
|
|
|
if (selection.isEmpty() || selection.size() > MAX_COPY_SIZE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
|
|
if (cursorOnAscii) {
|
2020-03-19 09:36:36 +00:00
|
|
|
clipboard->setText(Core()->cmdRawAt(QString("psx %1")
|
|
|
|
.arg(selection.size()),
|
|
|
|
selection.start()).trimmed());
|
2019-05-16 16:03:48 +00:00
|
|
|
} else {
|
2020-03-19 09:36:36 +00:00
|
|
|
clipboard->setText(Core()->cmdRawAt(QString("p8 %1")
|
|
|
|
.arg(selection.size()),
|
|
|
|
selection.start()).trimmed()); //TODO: copy in the format shown
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::copyAddress()
|
|
|
|
{
|
|
|
|
uint64_t addr = cursor.address;
|
|
|
|
if (!selection.isEmpty()) {
|
|
|
|
addr = selection.start();
|
|
|
|
}
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
|
|
clipboard->setText(RAddressString(addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::onRangeDialogAccepted()
|
|
|
|
{
|
|
|
|
if (rangeDialog.empty()) {
|
|
|
|
seek(rangeDialog.getStartAddress());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress());
|
|
|
|
}
|
|
|
|
|
2020-03-19 09:36:36 +00:00
|
|
|
void HexWidget::w_writeString()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
QInputDialog d;
|
|
|
|
d.setInputMode(QInputDialog::InputMode::TextInput);
|
|
|
|
QString str = d.getText(this, tr("Write string"),
|
|
|
|
tr("String:"), QLineEdit::Normal, "", &ok);
|
|
|
|
if (ok && !str.isEmpty()) {
|
|
|
|
Core()->cmdRawAt(QString("w %1")
|
|
|
|
.arg(str),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_increaseDecrease()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
IncrementDecrementDialog d;
|
|
|
|
int ret = d.exec();
|
|
|
|
if (ret == QDialog::Rejected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QString mode = d.getMode() == IncrementDecrementDialog::Increase ? "+" : "-";
|
|
|
|
Core()->cmdRawAt(QString("w%1%2 %3")
|
|
|
|
.arg(QString::number(d.getNBytes()))
|
|
|
|
.arg(mode)
|
|
|
|
.arg(QString::number(d.getValue())),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_writeZeros()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
QInputDialog d;
|
|
|
|
|
|
|
|
int size = 1;
|
|
|
|
if (!selection.isEmpty() && selection.size() <= INT_MAX) {
|
|
|
|
size = static_cast<int>(selection.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
QString str = QString::number(d.getInt(this, tr("Write zeros"),
|
|
|
|
tr("Number of zeros:"), size, 1, 0x7FFFFFFF, 1, &ok));
|
|
|
|
if (ok && !str.isEmpty()) {
|
|
|
|
Core()->cmdRawAt(QString("w0 %1")
|
|
|
|
.arg(str),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_write64()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Base64EnDecodedWriteDialog d;
|
|
|
|
int ret = d.exec();
|
|
|
|
if (ret == QDialog::Rejected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QString mode = d.getMode() == Base64EnDecodedWriteDialog::Encode ? "e" : "d";
|
|
|
|
QByteArray str = d.getData();
|
|
|
|
|
|
|
|
if (mode == "d" && (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;
|
|
|
|
}
|
|
|
|
|
|
|
|
Core()->cmdRawAt(QString("w6%1 %2")
|
|
|
|
.arg(mode)
|
|
|
|
.arg((mode == "e" ? str.toHex() : str).toStdString().c_str()),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_writeRandom()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
QInputDialog d;
|
|
|
|
|
|
|
|
int size = 1;
|
|
|
|
if (!selection.isEmpty() && selection.size() <= INT_MAX) {
|
|
|
|
size = static_cast<int>(selection.size());
|
|
|
|
}
|
|
|
|
QString nbytes = QString::number(d.getInt(this, tr("Write random"),
|
|
|
|
tr("Number of bytes:"), size, 1, 0x7FFFFFFF, 1, &ok));
|
|
|
|
if (ok && !nbytes.isEmpty()) {
|
|
|
|
Core()->cmdRawAt(QString("wr %1")
|
|
|
|
.arg(nbytes),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_duplFromOffset()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
DuplicateFromOffsetDialog d;
|
|
|
|
int ret = d.exec();
|
|
|
|
if (ret == QDialog::Rejected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
RVA copyFrom = d.getOffset();
|
|
|
|
QString nBytes = QString::number(d.getNBytes());
|
|
|
|
Core()->cmdRawAt(QString("wd %1 %2")
|
|
|
|
.arg(copyFrom)
|
|
|
|
.arg(nBytes),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_writePascalString()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
QInputDialog d;
|
|
|
|
d.setInputMode(QInputDialog::InputMode::TextInput);
|
|
|
|
QString str = d.getText(this, tr("Write Pascal string"),
|
|
|
|
tr("String:"), QLineEdit::Normal, "", &ok);
|
|
|
|
if (ok && !str.isEmpty()) {
|
|
|
|
Core()->cmdRawAt(QString("ws %1")
|
|
|
|
.arg(str),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_writeWideString()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
QInputDialog d;
|
|
|
|
d.setInputMode(QInputDialog::InputMode::TextInput);
|
|
|
|
QString str = d.getText(this, tr("Write wide string"),
|
|
|
|
tr("String:"), QLineEdit::Normal, "", &ok);
|
|
|
|
if (ok && !str.isEmpty()) {
|
|
|
|
Core()->cmdRawAt(QString("ww %1")
|
|
|
|
.arg(str),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::w_writeCString()
|
|
|
|
{
|
|
|
|
if (!ioModesController.prepareForWriting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
QInputDialog d;
|
|
|
|
d.setInputMode(QInputDialog::InputMode::TextInput);
|
|
|
|
QString str = d.getText(this, tr("Write zero-terminated string"),
|
|
|
|
tr("String:"), QLineEdit::Normal, "", &ok);
|
|
|
|
if (ok && !str.isEmpty()) {
|
|
|
|
Core()->cmdRawAt(QString("wz %1")
|
|
|
|
.arg(str),
|
|
|
|
getLocationAddress());
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
void HexWidget::updateItemLength()
|
|
|
|
{
|
|
|
|
itemPrefixLen = 0;
|
|
|
|
|
|
|
|
switch (itemFormat) {
|
|
|
|
case ItemFormatHex:
|
|
|
|
itemCharLen = 2 * itemByteLen;
|
|
|
|
if (itemByteLen > 1 && showExHex)
|
|
|
|
itemPrefixLen = hexPrefix.length();
|
|
|
|
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;
|
2019-06-18 08:34:30 +00:00
|
|
|
QRectF rect(itemArea.left(), 0, itemWidth(), lineHeight);
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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);
|
2019-05-25 07:02:20 +00:00
|
|
|
shadowCursor.screenPos.setWidth(cursorOnAscii ? itemWidth() : charWidth);
|
2019-05-16 16:03:48 +00:00
|
|
|
painter.drawRect(shadowCursor.screenPos);
|
|
|
|
painter.setPen(Qt::SolidLine);
|
|
|
|
}
|
|
|
|
|
|
|
|
painter.setPen(cursor.cachedColor);
|
2019-06-18 08:34:30 +00:00
|
|
|
QRectF charRect(cursor.screenPos);
|
2019-05-16 16:03:48 +00:00
|
|
|
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;
|
2019-06-18 08:34:30 +00:00
|
|
|
QSizeF areaSize((addrCharLen + (showExAddr ? 2 : 0)) * charWidth, lineHeight);
|
|
|
|
QRectF strRect(addrArea.topLeft(), areaSize);
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal vLineOffset = itemArea.left() - charWidth;
|
|
|
|
painter.drawLine(QLineF(vLineOffset, 0, vLineOffset, viewport()->height()));
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::drawItemArea(QPainter &painter)
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QRectF itemRect(itemArea.topLeft(), QSizeF(itemWidth(), lineHeight));
|
2019-05-16 16:03:48 +00:00
|
|
|
QColor itemColor;
|
|
|
|
QString itemString;
|
|
|
|
|
|
|
|
fillSelectionBackground(painter);
|
|
|
|
|
|
|
|
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);
|
2020-04-04 07:09:30 +00:00
|
|
|
|
|
|
|
if (!getFlagsAndComment(itemAddr).isEmpty()) {
|
|
|
|
QColor markerColor(borderColor);
|
|
|
|
markerColor.setAlphaF(0.5);
|
|
|
|
const auto shape = rangePolygons(itemAddr, itemAddr, false)[0];
|
|
|
|
painter.setPen(markerColor);
|
|
|
|
painter.drawPolyline(shape);
|
|
|
|
}
|
2019-06-17 12:13:34 +00:00
|
|
|
if (selection.contains(itemAddr) && !cursorOnAscii) {
|
2019-05-16 16:03:48 +00:00
|
|
|
itemColor = palette().highlightedText().color();
|
2019-06-17 12:13:34 +00:00
|
|
|
}
|
2019-11-30 14:55:29 +00:00
|
|
|
if (isItemDifferentAt(itemAddr)) {
|
|
|
|
itemColor.setRgb(diffColor.rgb());
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
painter.setPen(itemColor);
|
|
|
|
painter.drawText(itemRect, Qt::AlignVCenter, itemString);
|
|
|
|
itemRect.translate(itemWidth(), 0);
|
|
|
|
if (cursor.address == itemAddr) {
|
|
|
|
auto &itemCursor = cursorOnAscii ? shadowCursor : cursor;
|
|
|
|
itemCursor.cachedChar = itemString.at(0);
|
|
|
|
itemCursor.cachedColor = itemColor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
itemRect.translate(columnSpacingWidth(), 0);
|
|
|
|
}
|
|
|
|
itemRect.translate(0, lineHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
painter.setPen(borderColor);
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal vLineOffset = asciiArea.left() - charWidth;
|
|
|
|
painter.drawLine(QLineF(vLineOffset, 0, vLineOffset, viewport()->height()));
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::drawAsciiArea(QPainter &painter)
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QRectF charRect(asciiArea.topLeft(), QSizeF(charWidth, lineHeight));
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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);
|
2019-11-30 14:55:29 +00:00
|
|
|
if (selection.contains(address) && cursorOnAscii) {
|
2019-05-16 16:03:48 +00:00
|
|
|
color = palette().highlightedText().color();
|
2019-11-30 14:55:29 +00:00
|
|
|
}
|
|
|
|
if (isItemDifferentAt(address)) {
|
|
|
|
color.setRgb(diffColor.rgb());
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
painter.setPen(color);
|
|
|
|
/* Dots look ugly. Use fillRect() instead of drawText(). */
|
|
|
|
if (ascii == '.') {
|
2019-06-18 08:34:30 +00:00
|
|
|
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);
|
2019-05-16 16:03:48 +00:00
|
|
|
} 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)
|
|
|
|
{
|
2019-06-17 12:13:34 +00:00
|
|
|
if (selection.isEmpty()) {
|
2019-05-16 16:03:48 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-06-17 12:13:34 +00:00
|
|
|
const auto parts = rangePolygons(selection.start(), selection.end(), ascii);
|
2019-10-06 17:35:44 +00:00
|
|
|
for (const auto &shape : parts) {
|
2019-06-17 12:13:34 +00:00
|
|
|
QColor highlightColor = palette().color(QPalette::Highlight);
|
|
|
|
if (ascii == cursorOnAscii) {
|
|
|
|
painter.setBrush(highlightColor);
|
|
|
|
painter.drawPolygon(shape);
|
|
|
|
} else {
|
|
|
|
painter.setPen(highlightColor);
|
|
|
|
painter.drawPolyline(shape);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-17 12:13:34 +00:00
|
|
|
QVector<QPolygonF> HexWidget::rangePolygons(RVA start, RVA last, bool ascii)
|
|
|
|
{
|
|
|
|
if (last < startAddress || start > lastVisibleAddr()) {
|
|
|
|
return {};
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
2019-06-17 12:13:34 +00:00
|
|
|
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<QPolygonF> parts;
|
|
|
|
|
|
|
|
auto getRectangle = [&](int offset) {
|
|
|
|
return QRectF(ascii ? asciiRectangle(offset) : itemRectangle(offset));
|
|
|
|
};
|
|
|
|
|
|
|
|
auto startRect = getRectangle(startOffset);
|
|
|
|
auto endRect = getRectangle(endOffset);
|
|
|
|
if (!ascii) {
|
|
|
|
if (int startFraction = startOffset % itemByteLen) {
|
|
|
|
startRect.setLeft(startRect.left() + startFraction * startRect.width() / itemByteLen);
|
|
|
|
}
|
|
|
|
if (int endFraction = itemByteLen - 1 - (endOffset % itemByteLen)) {
|
|
|
|
endRect.setRight(endRect.right() - endFraction * endRect.width() / itemByteLen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (endOffset - startOffset + 1 <= rowSizeBytes) {
|
|
|
|
if (startOffset / rowSizeBytes == endOffset / rowSizeBytes) { // single row
|
|
|
|
rect = startRect;
|
|
|
|
rect.setRight(endRect.right());
|
|
|
|
parts.push_back(QPolygonF(rect));
|
2019-05-16 16:03:48 +00:00
|
|
|
} else {
|
2020-01-28 08:02:17 +00:00
|
|
|
// two separate rectangles
|
2019-06-17 12:13:34 +00:00
|
|
|
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();
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
2019-06-17 12:13:34 +00:00
|
|
|
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);
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
2019-06-17 12:13:34 +00:00
|
|
|
return parts;
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::updateMetrics()
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QFontMetricsF fontMetrics(this->monospaceFont);
|
|
|
|
lineHeight = fontMetrics.height();
|
|
|
|
charWidth = fontMetrics.width(QLatin1Char('F'));
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
updateCounts();
|
|
|
|
updateAreasHeight();
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal cursorWidth = std::max(charWidth / 3, 1.);
|
2019-05-16 16:03:48 +00:00
|
|
|
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()
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
const qreal spacingWidth = areaSpacingWidth();
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal yOffset = showHeader ? lineHeight : 0;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
addrArea.setTopLeft(QPointF(0, yOffset));
|
2019-05-16 16:03:48 +00:00
|
|
|
addrArea.setWidth((addrCharLen + (showExAddr ? 2 : 0)) * charWidth);
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
itemArea.setTopLeft(QPointF(addrArea.right() + spacingWidth, yOffset));
|
2019-05-16 16:03:48 +00:00
|
|
|
itemArea.setWidth(itemRowWidth());
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
asciiArea.setTopLeft(QPointF(itemArea.right() + spacingWidth, yOffset));
|
2019-05-16 16:03:48 +00:00
|
|
|
asciiArea.setWidth(asciiRowWidth());
|
|
|
|
|
|
|
|
updateWidth();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::updateAreasHeight()
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
visibleLines = static_cast<int>((viewport()->height() - itemArea.top()) / lineHeight);
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal height = visibleLines * lineHeight;
|
2019-05-16 16:03:48 +00:00
|
|
|
addrArea.setHeight(height);
|
|
|
|
itemArea.setHeight(height);
|
|
|
|
asciiArea.setHeight(height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::moveCursor(int offset, bool select)
|
|
|
|
{
|
|
|
|
BasicCursor addr = cursor.address;
|
|
|
|
addr += offset;
|
|
|
|
if (addr.address > data->maxIndex()) {
|
|
|
|
addr.address = data->maxIndex();
|
|
|
|
}
|
|
|
|
setCursorAddr(addr, select);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setCursorAddr(BasicCursor addr, bool select)
|
|
|
|
{
|
|
|
|
if (!select) {
|
|
|
|
bool clearingSelection = !selection.isEmpty();
|
|
|
|
selection.init(addr);
|
|
|
|
if (clearingSelection)
|
|
|
|
emit selectionChanged(getSelection());
|
|
|
|
}
|
|
|
|
emit positionChanged(addr.address);
|
|
|
|
|
|
|
|
cursor.address = addr.address;
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
if (addressValue == startAddress + bytesPerScreen()) {
|
|
|
|
startAddress += itemRowByteLen();
|
|
|
|
} else {
|
|
|
|
startAddress = addressValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
fetchData();
|
2019-11-30 14:55:29 +00:00
|
|
|
|
|
|
|
if (startAddress > (data->maxIndex() - bytesPerScreen()) + 1) {
|
|
|
|
startAddress = (data->maxIndex() - bytesPerScreen()) + 1;
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateCursorMeta();
|
|
|
|
|
|
|
|
/* Draw cursor */
|
|
|
|
cursor.isVisible = !select;
|
|
|
|
viewport()->update();
|
|
|
|
|
|
|
|
/* Resume cursor repainting */
|
|
|
|
cursorEnabled = selection.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::updateCursorMeta()
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QPointF point;
|
|
|
|
QPointF pointAscii;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
cursor.screenPos.moveTopLeft(cursorOnAscii ? pointAscii : point);
|
|
|
|
shadowCursor.screenPos.moveTopLeft(cursorOnAscii ? point : pointAscii);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexWidget::setCursorOnAscii(bool ascii)
|
|
|
|
{
|
|
|
|
cursorOnAscii = ascii;
|
|
|
|
}
|
|
|
|
|
|
|
|
const 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;
|
|
|
|
}
|
|
|
|
|
2019-10-06 17:35:44 +00:00
|
|
|
template<class T>
|
|
|
|
static T fromBigEndian(const void * src)
|
|
|
|
{
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
|
|
|
|
return qFromBigEndian<T>(src);
|
|
|
|
#else
|
|
|
|
T result;
|
|
|
|
memcpy(&result, src, sizeof(T));
|
|
|
|
return qFromBigEndian<T>(result);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
static T fromLittleEndian(const void * src)
|
|
|
|
{
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
|
|
|
|
return qFromLittleEndian<T>(src);
|
|
|
|
#else
|
|
|
|
T result;
|
|
|
|
memcpy(&result, src, sizeof(T));
|
|
|
|
return qFromLittleEndian<T>(result);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
QVariant HexWidget::readItem(int offset, QColor *color)
|
|
|
|
{
|
|
|
|
quint8 byte;
|
|
|
|
quint16 word;
|
|
|
|
quint32 dword;
|
|
|
|
quint64 qword;
|
2020-04-16 17:32:24 +00:00
|
|
|
float float32;
|
|
|
|
double float64;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2020-04-16 17:32:24 +00:00
|
|
|
quint8 bytes[sizeof(uint64_t)];
|
|
|
|
data->copy(bytes, startAddress + offset, static_cast<size_t>(itemByteLen));
|
2019-05-16 16:03:48 +00:00
|
|
|
const bool signedItem = itemFormat == ItemFormatSignedDec;
|
|
|
|
|
2020-04-16 17:32:24 +00:00
|
|
|
if (color) {
|
|
|
|
*color = defColor;
|
|
|
|
}
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
switch (itemByteLen) {
|
|
|
|
case 1:
|
2020-04-16 17:32:24 +00:00
|
|
|
byte = bytes[0];
|
2019-05-16 16:03:48 +00:00
|
|
|
if (color)
|
|
|
|
*color = itemColor(byte);
|
|
|
|
if (!signedItem)
|
|
|
|
return QVariant(static_cast<quint64>(byte));
|
|
|
|
return QVariant(static_cast<qint64>(static_cast<qint8>(byte)));
|
|
|
|
case 2:
|
|
|
|
if (itemBigEndian)
|
2020-04-16 17:32:24 +00:00
|
|
|
word = fromBigEndian<quint16>(bytes);
|
2019-05-16 16:03:48 +00:00
|
|
|
else
|
2020-04-16 17:32:24 +00:00
|
|
|
word = fromLittleEndian<quint16>(bytes);
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
if (!signedItem)
|
|
|
|
return QVariant(static_cast<quint64>(word));
|
|
|
|
return QVariant(static_cast<qint64>(static_cast<qint16>(word)));
|
|
|
|
case 4:
|
|
|
|
if (itemBigEndian)
|
2020-04-16 17:32:24 +00:00
|
|
|
dword = fromBigEndian<quint32>(bytes);
|
2019-05-16 16:03:48 +00:00
|
|
|
else
|
2020-04-16 17:32:24 +00:00
|
|
|
dword = fromLittleEndian<quint32>(bytes);
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
if (itemFormat == ItemFormatFloat) {
|
2020-04-16 17:32:24 +00:00
|
|
|
memcpy(&float32, &dword, sizeof(float32));
|
|
|
|
return QVariant(float32);
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
if (!signedItem)
|
|
|
|
return QVariant(static_cast<quint64>(dword));
|
|
|
|
return QVariant(static_cast<qint64>(static_cast<qint32>(dword)));
|
|
|
|
case 8:
|
|
|
|
if (itemBigEndian)
|
2020-04-16 17:32:24 +00:00
|
|
|
qword = fromBigEndian<quint64>(bytes);
|
2019-05-16 16:03:48 +00:00
|
|
|
else
|
2020-04-16 17:32:24 +00:00
|
|
|
qword = fromLittleEndian<quint64>(bytes);
|
2019-05-16 16:03:48 +00:00
|
|
|
if (itemFormat == ItemFormatFloat) {
|
2020-04-16 17:32:24 +00:00
|
|
|
memcpy(&float64, &qword, sizeof(float64));
|
|
|
|
return QVariant(float64);
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
if (!signedItem)
|
|
|
|
return QVariant(qword);
|
|
|
|
return QVariant(static_cast<qint64>(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);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
QChar HexWidget::renderAscii(int offset, QColor *color)
|
|
|
|
{
|
2020-04-16 17:32:24 +00:00
|
|
|
uchar byte;
|
|
|
|
data->copy(&byte, startAddress + offset, sizeof(byte));
|
2019-05-16 16:03:48 +00:00
|
|
|
if (color) {
|
|
|
|
*color = itemColor(byte);
|
|
|
|
}
|
|
|
|
if (!IS_PRINTABLE(byte)) {
|
|
|
|
byte = '.';
|
|
|
|
}
|
|
|
|
return QChar(byte);
|
|
|
|
}
|
|
|
|
|
2020-04-04 07:09:30 +00:00
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
2019-05-16 16:03:48 +00:00
|
|
|
void HexWidget::fetchData()
|
|
|
|
{
|
2019-11-30 14:55:29 +00:00
|
|
|
data.swap(oldData);
|
2019-05-16 16:03:48 +00:00
|
|
|
data->fetch(startAddress, bytesPerScreen());
|
|
|
|
}
|
|
|
|
|
|
|
|
BasicCursor HexWidget::screenPosToAddr(const QPoint &point, bool middle) const
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QPointF pt = point - itemArea.topLeft();
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
int relativeAddress = 0;
|
2019-06-18 08:34:30 +00:00
|
|
|
int line = static_cast<int>(pt.y() / lineHeight);
|
|
|
|
relativeAddress += line * itemRowByteLen();
|
|
|
|
int column = static_cast<int>(pt.x() / columnExWidth());
|
|
|
|
relativeAddress += column * itemGroupByteLen();
|
|
|
|
pt.rx() -= column * columnExWidth();
|
2019-05-16 16:03:48 +00:00
|
|
|
auto roundingOffset = middle ? itemWidth() / 2 : 0;
|
2019-06-18 08:34:30 +00:00
|
|
|
relativeAddress += static_cast<int>((pt.x() + roundingOffset) / itemWidth()) * itemByteLen;
|
2019-05-16 16:03:48 +00:00
|
|
|
BasicCursor result(startAddress);
|
|
|
|
result += relativeAddress;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
BasicCursor HexWidget::asciiPosToAddr(const QPoint &point, bool middle) const
|
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QPointF pt = point - asciiArea.topLeft();
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
int relativeAddress = 0;
|
2019-06-18 08:34:30 +00:00
|
|
|
relativeAddress += static_cast<int>(pt.y() / lineHeight) * itemRowByteLen();
|
2019-05-16 16:03:48 +00:00
|
|
|
auto roundingOffset = middle ? (charWidth / 2) : 0;
|
2019-06-18 08:34:30 +00:00
|
|
|
relativeAddress += static_cast<int>((pt.x() + (roundingOffset)) / charWidth);
|
2019-05-16 16:03:48 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-04-16 17:32:24 +00:00
|
|
|
QRectF HexWidget::itemRectangle(int offset)
|
2019-05-16 16:03:48 +00:00
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal x;
|
|
|
|
qreal y;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
qreal width = itemWidth();
|
2019-05-16 16:03:48 +00:00
|
|
|
y = (offset / itemRowByteLen()) * lineHeight;
|
|
|
|
offset %= itemRowByteLen();
|
|
|
|
|
|
|
|
x = (offset / itemGroupByteLen()) * columnExWidth();
|
|
|
|
offset %= itemGroupByteLen();
|
|
|
|
x += (offset / itemByteLen) * itemWidth();
|
2019-06-17 12:13:34 +00:00
|
|
|
if (offset == 0) {
|
|
|
|
x -= charWidth / 2;
|
|
|
|
width += charWidth / 2;
|
|
|
|
}
|
2019-07-09 18:55:07 +00:00
|
|
|
if (static_cast<int>(offset) == itemGroupByteLen() - 1) {
|
2019-06-17 12:13:34 +00:00
|
|
|
width += charWidth / 2;
|
|
|
|
}
|
2019-05-16 16:03:48 +00:00
|
|
|
|
|
|
|
x += itemArea.x();
|
|
|
|
y += itemArea.y();
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
return QRectF(x, y, width, lineHeight);
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 17:32:24 +00:00
|
|
|
QRectF HexWidget::asciiRectangle(int offset)
|
2019-05-16 16:03:48 +00:00
|
|
|
{
|
2019-06-18 08:34:30 +00:00
|
|
|
QPointF p;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
p.ry() = (offset / itemRowByteLen()) * lineHeight;
|
2019-05-16 16:03:48 +00:00
|
|
|
offset %= itemRowByteLen();
|
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
p.rx() = offset * charWidth;
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
p += asciiArea.topLeft();
|
2019-05-16 16:03:48 +00:00
|
|
|
|
2019-06-18 08:34:30 +00:00
|
|
|
return QRectF(p, QSizeF(charWidth, lineHeight));
|
2019-05-16 16:03:48 +00:00
|
|
|
}
|
2020-03-19 09:36:36 +00:00
|
|
|
|
|
|
|
RVA HexWidget::getLocationAddress() {
|
|
|
|
return !selection.isEmpty() ? selection.start() : cursor.address;
|
|
|
|
}
|