diff --git a/src/Cutter.cpp b/src/Cutter.cpp index 613920ad..778d6285 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -55,7 +55,7 @@ CutterCore::CutterCore(QObject *parent) : // Otherwise r2 may ask the user for input and Cutter would freeze setConfig("scr.interactive", false); - + #if defined(APPIMAGE) || defined(MACOS_R2_BUNDLED) auto prefix = QDir(QCoreApplication::applicationDirPath()); # ifdef APPIMAGE diff --git a/src/Cutter.pro b/src/Cutter.pro index f45d000b..764075dd 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -155,6 +155,7 @@ SOURCES += \ utils/NestedIPyKernel.cpp \ dialogs/R2PluginsDialog.cpp \ widgets/CutterDockWidget.cpp \ + widgets/CutterSeekableWidget.cpp \ widgets/GraphWidget.cpp \ utils/JsonTreeItem.cpp \ utils/JsonModel.cpp \ @@ -229,6 +230,7 @@ HEADERS += \ utils/NestedIPyKernel.h \ dialogs/R2PluginsDialog.h \ widgets/CutterDockWidget.h \ + widgets/CutterSeekableWidget.h \ widgets/GraphWidget.h \ utils/JsonTreeItem.h \ utils/JsonModel.h \ diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f98fc565..f8e6c704 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -223,6 +223,32 @@ void MainWindow::initUI() connect(core, SIGNAL(projectSaved(const QString &)), this, SLOT(projectSaved(const QString &))); } +void MainWindow::on_actionExtraGraph_triggered() +{ + QDockWidget *extraDock = new GraphWidget(this, 0); + addExtraWidget(extraDock); +} + +void MainWindow::on_actionExtraHexdump_triggered() +{ + QDockWidget *extraDock = new HexdumpWidget(this, 0); + addExtraWidget(extraDock); +} + +void MainWindow::on_actionExtraDisassembly_triggered() +{ + QDockWidget *extraDock = new DisassemblyWidget(this, 0); + addExtraWidget(extraDock); +} + +void MainWindow::addExtraWidget(QDockWidget *extraDock) +{ + addDockWidget(Qt::TopDockWidgetArea, extraDock); + auto restoreExtraDock = qhelpers::forceWidth(extraDock->widget(), 600); + qApp->processEvents(); + restoreExtraDock.restoreWidth(extraDock->widget()); +} + void MainWindow::openNewFile(const QString &fn, int analLevel, QList advancedOptions) { setFilename(fn); @@ -525,6 +551,23 @@ void MainWindow::showDefaultDocks() updateDockActionsChecked(); } +void MainWindow::showZenDocks() +{ + const QList zenDocks = { functionsDock, + stringsDock, + graphDock, + disassemblyDock, + hexdumpDock, + searchDock + }; + for (auto w : dockWidgets) { + if (zenDocks.contains(w)) { + w->show(); + } + } + updateDockActionsChecked(); +} + void MainWindow::resetToDefaultLayout() { hideAllDocks(); @@ -545,6 +588,24 @@ void MainWindow::resetToDefaultLayout() Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly); } +void MainWindow::resetToZenLayout() +{ + hideAllDocks(); + restoreDocks(); + showZenDocks(); + disassemblyDock->raise(); + + // ugly workaround to set the default widths of functions + // if anyone finds a way to do this cleaner that also works, feel free to change it! + auto restoreFunctionDock = qhelpers::forceWidth(functionsDock->widget(), 200); + + qApp->processEvents(); + + restoreFunctionDock.restoreWidth(functionsDock->widget()); + + Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly); +} + void MainWindow::addOutput(const QString &msg) { consoleDock->addOutput(msg); @@ -590,6 +651,11 @@ void MainWindow::on_actionDefault_triggered() resetToDefaultLayout(); } +void MainWindow::on_actionZen_triggered() +{ + resetToZenLayout(); +} + void MainWindow::on_actionNew_triggered() { on_actionOpen_triggered(); @@ -761,7 +827,6 @@ bool MainWindow::eventFilter(QObject *, QEvent *event) return true; } } - return false; } diff --git a/src/MainWindow.h b/src/MainWindow.h index 6490ffad..abe16351 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -95,6 +95,8 @@ public: void addToDockWidgetList(QDockWidget *dockWidget); void addDockWidgetAction(QDockWidget *dockWidget, QAction *action); + void addExtraWidget(QDockWidget *extraDock); + public slots: @@ -119,12 +121,16 @@ public slots: private slots: void on_actionAbout_triggered(); + void on_actionExtraGraph_triggered(); + void on_actionExtraHexdump_triggered(); + void on_actionExtraDisassembly_triggered(); void on_actionRefresh_Panels_triggered(); void on_actionDisasAdd_comment_triggered(); void on_actionDefault_triggered(); + void on_actionZen_triggered(); void on_actionFunctionsRename_triggered(); @@ -211,10 +217,12 @@ private: #endif void resetToDefaultLayout(); + void resetToZenLayout(); void restoreDocks(); void hideAllDocks(); void showDefaultDocks(); + void showZenDocks(); void updateDockActionsChecked(); void toggleDockWidget(QDockWidget *dock_widget, bool show); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index ded96d79..d22c22e2 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -201,6 +201,7 @@ border-top: 0px; + @@ -256,6 +257,8 @@ border-top: 0px; + + @@ -322,6 +325,14 @@ background-color: palette(dark); Reset layout + + + Zen Mode + + + Zen mode + + About @@ -1093,6 +1104,29 @@ background-color: palette(dark); Jupyter + + + Graph view + + + + + Hexdump view + + + + + Disassembly view + + + + + Add extra... + + + + + diff --git a/src/widgets/CutterDockWidget.cpp b/src/widgets/CutterDockWidget.cpp index 07b9513f..710d43e6 100644 --- a/src/widgets/CutterDockWidget.cpp +++ b/src/widgets/CutterDockWidget.cpp @@ -6,8 +6,8 @@ CutterDockWidget::CutterDockWidget(MainWindow *main, QAction *action) : QDockWidget(main), action(action) { - main->addToDockWidgetList(this); if (action) { + main->addToDockWidgetList(this); main->addDockWidgetAction(this, action); connect(action, &QAction::triggered, this, &CutterDockWidget::toggleDockWidget); } diff --git a/src/widgets/CutterSeekableWidget.cpp b/src/widgets/CutterSeekableWidget.cpp new file mode 100644 index 00000000..bd0e74c4 --- /dev/null +++ b/src/widgets/CutterSeekableWidget.cpp @@ -0,0 +1,67 @@ +#include "MainWindow.h" +#include "CutterSeekableWidget.h" + +CutterSeekableWidget::CutterSeekableWidget(QObject *parent) + : + QObject(parent) +{ + connect(Core(), &CutterCore::seekChanged, this, &CutterSeekableWidget::onSeekChanged); +} + +void CutterSeekableWidget::onSeekChanged(RVA addr) +{ + if (isInSyncWithCore) { + emit seekChanged(addr); + } +} + +void CutterSeekableWidget::seek(RVA addr) +{ + if (isInSyncWithCore) { + Core()->seek(addr); + } + else { + prevIdenpendentOffset = independentOffset; + independentOffset = addr; + emit seekChanged(addr); + } +} + +RVA CutterSeekableWidget::getOffset() +{ + RVA addr; + if (isInSyncWithCore) { + addr = Core()->getOffset(); + } + else { + addr = independentOffset; + } + return addr; +} + +void CutterSeekableWidget::toggleSyncWithCore() +{ + isInSyncWithCore = !isInSyncWithCore; +} + +RVA CutterSeekableWidget::getIndependentOffset() +{ + return independentOffset; +} + +RVA CutterSeekableWidget::getPrevIndependentOffset() +{ + return prevIdenpendentOffset; +} + +bool CutterSeekableWidget::getSyncWithCore() +{ + return isInSyncWithCore; +} + +void CutterSeekableWidget::setIndependentOffset(RVA addr) +{ + independentOffset = addr; +} + +CutterSeekableWidget::~CutterSeekableWidget() {} \ No newline at end of file diff --git a/src/widgets/CutterSeekableWidget.h b/src/widgets/CutterSeekableWidget.h new file mode 100644 index 00000000..9b623acb --- /dev/null +++ b/src/widgets/CutterSeekableWidget.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Cutter.h" + +class MainWindow; + +class CutterSeekableWidget : public QObject +{ + + Q_OBJECT + +public: + explicit CutterSeekableWidget(QObject *parent = nullptr); + ~CutterSeekableWidget(); + void seek(RVA addr); + void toggleSyncWithCore(); + RVA getOffset(); + RVA getIndependentOffset(); + RVA getPrevIndependentOffset(); + bool getSyncWithCore(); + void setIndependentOffset(RVA addr); + void onSeekChanged(RVA addr); + +private: + RVA independentOffset = RVA_INVALID; + RVA prevIdenpendentOffset = RVA_INVALID; + bool isInSyncWithCore = true; + +signals: + void seekChanged(RVA addr); + +}; \ No newline at end of file diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 568c2f07..1087c155 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -1,5 +1,5 @@ #include "DisassemblerGraphView.h" - +#include "CutterSeekableWidget.h" #include #include #include @@ -20,7 +20,8 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent) : GraphView(parent), mFontMetrics(nullptr), - mMenu(new DisassemblyContextMenu(this)) + mMenu(new DisassemblyContextMenu(this)), + seekable(new CutterSeekableWidget(this)) { highlight_token = nullptr; // Signals that require a refresh all @@ -99,16 +100,21 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent) mMenu->addAction(&actionExportGraph); connect(&actionExportGraph, SIGNAL(triggered(bool)), this, SLOT(on_actionExportGraph_triggered())); + mMenu->addSeparator(); + actionSyncOffset.setText(tr("Sync/unsync offset")); + mMenu->addAction(&actionSyncOffset); + + connect(&actionSyncOffset, SIGNAL(triggered(bool)), this, SLOT(toggleSync())); initFont(); colorsUpdatedSlot(); } -void DisassemblerGraphView::connectSeekChanged(bool disconnect) +void DisassemblerGraphView::connectSeekChanged(bool disconn) { - if (disconnect) { - QObject::disconnect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA))); + if (disconn) { + disconnect(seekable, &CutterSeekableWidget::seekChanged, this, &DisassemblerGraphView::onSeekChanged); } else { - connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA))); + connect(seekable, &CutterSeekableWidget::seekChanged, this, &DisassemblerGraphView::onSeekChanged); } } @@ -119,6 +125,17 @@ DisassemblerGraphView::~DisassemblerGraphView() } } +void DisassemblerGraphView::toggleSync() +{ + seekable->toggleSyncWithCore(); + if (seekable->getSyncWithCore()) { + parentWidget()->setWindowTitle(windowTitle); + } else { + parentWidget()->setWindowTitle(windowTitle + " (not synced)"); + seekable->setIndependentOffset(Core()->getOffset()); + } +} + void DisassemblerGraphView::refreshView() { initFont(); @@ -134,7 +151,7 @@ void DisassemblerGraphView::loadCurrentGraph() .set("asm.bbline", false) .set("asm.lines", false) .set("asm.lines.fcn", false); - QJsonDocument functionsDoc = Core()->cmdj("agJ"); + QJsonDocument functionsDoc = Core()->cmdj("agJ " + RAddressString(seekable->getOffset())); QJsonArray functions = functionsDoc.array(); disassembly_blocks.clear(); @@ -149,12 +166,16 @@ void DisassemblerGraphView::loadCurrentGraph() f.ready = true; f.entry = func["offset"].toVariant().toULongLong(); - QString windowTitle = tr("Graph"); + windowTitle = tr("Graph"); QString funcName = func["name"].toString().trimmed(); if (!funcName.isEmpty()) { windowTitle += " (" + funcName + ")"; } - parentWidget()->setWindowTitle(windowTitle); + if (!seekable->getSyncWithCore()) { + parentWidget()->setWindowTitle(windowTitle + " (not synced)"); + } else { + parentWidget()->setWindowTitle(windowTitle); + } RVA entry = func["offset"].toVariant().toULongLong(); @@ -297,7 +318,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block) // Figure out if the current block is selected for (const Instr &instr : db.instrs) { - RVA addr = Core()->getOffset(); + RVA addr = seekable->getOffset(); if ((instr.addr <= addr) && (addr <= instr.addr + instr.size)) { block_selected = true; selected_instruction = instr.addr; @@ -543,27 +564,27 @@ void DisassemblerGraphView::zoomReset() void DisassemblerGraphView::takeTrue() { - DisassemblyBlock *db = blockForAddress(Core()->getOffset()); + DisassemblyBlock *db = blockForAddress(seekable->getOffset()); if (db->true_path != RVA_INVALID) { - Core()->seek(db->true_path); + seekable->seek(db->true_path); } else if (blocks[db->entry].exits.size()) { - Core()->seek(blocks[db->entry].exits[0]); + seekable->seek(blocks[db->entry].exits[0]); } } void DisassemblerGraphView::takeFalse() { - DisassemblyBlock *db = blockForAddress(Core()->getOffset()); + DisassemblyBlock *db = blockForAddress(seekable->getOffset()); if (db->false_path != RVA_INVALID) { - Core()->seek(db->false_path); + seekable->seek(db->false_path); } else if (blocks[db->entry].exits.size()) { - Core()->seek(blocks[db->entry].exits[0]); + seekable->seek(blocks[db->entry].exits[0]); } } void DisassemblerGraphView::seekInstruction(bool previous_instr) { - RVA addr = Core()->getOffset(); + RVA addr = seekable->getOffset(); DisassemblyBlock *db = blockForAddress(addr); if (!db) { return; @@ -577,9 +598,9 @@ void DisassemblerGraphView::seekInstruction(bool previous_instr) // Found the instructon. Check if a next one exists if (!previous_instr && (i < db->instrs.size() - 1)) { - seek(db->instrs[i + 1].addr, true); + seekable->seek(db->instrs[i + 1].addr); } else if (previous_instr && (i > 0)) { - seek(db->instrs[i - 1].addr); + seekable->seek(db->instrs[i - 1].addr); } } } @@ -594,10 +615,10 @@ void DisassemblerGraphView::prevInstr() seekInstruction(true); } -void DisassemblerGraphView::seek(RVA addr, bool update_viewport) +void DisassemblerGraphView::seekLocal(RVA addr, bool update_viewport) { connectSeekChanged(true); - Core()->seek(addr); + seekable->seek(addr); connectSeekChanged(false); if (update_viewport) { viewport()->update(); @@ -606,7 +627,12 @@ void DisassemblerGraphView::seek(RVA addr, bool update_viewport) void DisassemblerGraphView::seekPrev() { - Core()->seekPrev(); + if (seekable->getSyncWithCore()) { + Core()->seekPrev(); + } + else { + seekable->seek(seekable->getPrevIndependentOffset()); + } } void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, @@ -617,7 +643,7 @@ void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEve return; } - seek(instr, true); + seekLocal(instr); if (event->button() == Qt::RightButton) { mMenu->setOffset(instr); @@ -629,13 +655,14 @@ void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMo QPoint pos) { Q_UNUSED(event); + RVA instr = getAddrForMouseEvent(block, &pos); if (instr == RVA_INVALID) { return; } QList refs = Core()->getXRefs(instr, false, false); if (refs.length()) { - Core()->seek(refs.at(0).to); + seekable->seek(refs.at(0).to); } if (refs.length() > 1) { qWarning() << "Too many references here. Weird behaviour expected."; @@ -671,7 +698,7 @@ void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to) transition_dont_seek = false; return; } - seek(to->entry); + seekLocal(to->entry); } diff --git a/src/widgets/DisassemblerGraphView.h b/src/widgets/DisassemblerGraphView.h index c7378e91..04f0b497 100644 --- a/src/widgets/DisassemblerGraphView.h +++ b/src/widgets/DisassemblerGraphView.h @@ -10,6 +10,7 @@ #include "widgets/GraphView.h" #include "menus/DisassemblyContextMenu.h" #include "utils/RichTextPainter.h" +#include "CutterSeekableWidget.h" class DisassemblerGraphView : public GraphView { @@ -142,6 +143,7 @@ public: virtual void blockTransitionedTo(GraphView::GraphBlock *to) override; void loadCurrentGraph(); + QString windowTitle; // bool navigate(ut64 addr); public slots: @@ -149,6 +151,7 @@ public slots: void colorsUpdatedSlot(); void fontsUpdatedSlot(); void onSeekChanged(RVA addr); + void toggleSync(); void zoomIn(QPoint mouse = QPoint(0, 0)); void zoomOut(QPoint mouse = QPoint(0, 0)); @@ -189,9 +192,9 @@ private: RVA getAddrForMouseEvent(GraphBlock &block, QPoint *point); Instr *getInstrForMouseEvent(GraphBlock &block, QPoint *point); DisassemblyBlock *blockForAddress(RVA addr); - void seek(RVA addr, bool update_viewport = true); + void seekLocal(RVA addr, bool update_viewport = true); void seekInstruction(bool previous_instr); - + CutterSeekableWidget *seekable = nullptr; QList shortcuts; QColor disassemblyBackgroundColor; @@ -218,6 +221,7 @@ private: QColor mDisabledBreakpointColor; QAction actionExportGraph; + QAction actionSyncOffset; }; #endif // DISASSEMBLERGRAPHVIEW_H diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 243ea83a..9cc3f858 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -41,6 +41,7 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) , mCtxMenu(new DisassemblyContextMenu(this)) , mDisasScrollArea(new DisassemblyScrollArea(this)) , mDisasTextEdit(new DisassemblyTextEdit(this)) + , seekable(new CutterSeekableWidget(this)) { topOffset = bottomOffset = RVA_INVALID; cursorLineOffset = 0; @@ -104,7 +105,6 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) } }); - connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA))); connect(Core(), SIGNAL(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)), this, SLOT(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType))); connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshDisasm())); @@ -140,6 +140,11 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) shortcut_escape->setContext(Qt::WidgetShortcut); connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev())); + mCtxMenu->addSeparator(); + syncIt.setText(tr("Sync/unsync offset")); + mCtxMenu->addAction(&syncIt); + connect(&syncIt, SIGNAL(triggered(bool)), this, SLOT(toggleSync())); + connect(seekable, &CutterSeekableWidget::seekChanged, this, &DisassemblyWidget::on_seekChanged); #define ADD_SHORTCUT(ksq, slot) { \ QShortcut *s = new QShortcut((ksq), this); \ @@ -167,6 +172,18 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) #undef ADD_SHORTCUT } +void DisassemblyWidget::toggleSync() +{ + QString windowTitle = tr("Disassembly"); + seekable->toggleSyncWithCore(); + if (seekable->getSyncWithCore()) { + setWindowTitle(windowTitle); + } else { + setWindowTitle(windowTitle + " (not synced)"); + seekable->setIndependentOffset(Core()->getOffset()); + } +} + QWidget *DisassemblyWidget::getTextWidget() { return mDisasTextEdit; @@ -354,7 +371,7 @@ RVA DisassemblyWidget::readDisassemblyOffset(QTextCursor tc) void DisassemblyWidget::updateCursorPosition() { - RVA offset = Core()->getOffset(); + RVA offset = seekable->getOffset(); // already fine where it is? RVA currentLineOffset = readCurrentDisassemblyOffset(); @@ -432,7 +449,7 @@ void DisassemblyWidget::cursorPositionChanged() } seekFromCursor = true; - Core()->seek(offset); + seekable->seek(offset); seekFromCursor = false; highlightCurrentLine(); mCtxMenu->setCanCopy(mDisasTextEdit->textCursor().hasSelection()); @@ -492,8 +509,8 @@ void DisassemblyWidget::moveCursorRelative(bool up, bool page) // handle cases where top instruction offsets change RVA offset = readCurrentDisassemblyOffset(); - if (offset != Core()->getOffset()) { - Core()->seek(offset); + if (offset != seekable->getOffset()) { + seekable->seek(offset); highlightCurrentLine(); } } @@ -520,7 +537,7 @@ bool DisassemblyWidget::eventFilter(QObject *obj, QEvent *event) } if (jump != RVA_INVALID) { - Core()->seek(jump); + seekable->seek(jump); } return true; diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index db7965cb..9b7a45a2 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -3,9 +3,11 @@ #include "Cutter.h" #include "CutterDockWidget.h" +#include "CutterSeekableWidget.h" #include #include #include +#include class DisassemblyTextEdit; @@ -26,6 +28,7 @@ public slots: void fontsUpdatedSlot(); void colorsUpdatedSlot(); void seekPrev(); + void toggleSync(); private slots: void on_seekChanged(RVA offset); @@ -63,6 +66,8 @@ private: void connectCursorPositionChanged(bool disconnect); void moveCursorRelative(bool up, bool page); + QAction syncIt; + CutterSeekableWidget *seekable; }; class DisassemblyScrollArea : public QAbstractScrollArea diff --git a/src/widgets/GraphWidget.cpp b/src/widgets/GraphWidget.cpp index a5483153..5f713df2 100644 --- a/src/widgets/GraphWidget.cpp +++ b/src/widgets/GraphWidget.cpp @@ -23,9 +23,6 @@ GraphWidget::GraphWidget(MainWindow *main, QAction *action) : this->graphView->setFocus(); } }); - - - } GraphWidget::~GraphWidget() {} diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index 57fa8dde..d19bd73b 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -15,7 +15,8 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : CutterDockWidget(main, action), - ui(new Ui::HexdumpWidget) + ui(new Ui::HexdumpWidget), + seekable(new CutterSeekableWidget(this)) { ui->setupUi(this); @@ -44,6 +45,8 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : colorsUpdatedSlot(); updateHeaders(); + this->setWindowTitle(tr("Hexdump")); + connect(&syncAction, SIGNAL(triggered(bool)), this, SLOT(toggleSync())); // Set hexdump context menu ui->hexHexText->setContextMenuPolicy(Qt::CustomContextMenu); @@ -61,7 +64,6 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated())); connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot())); - connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA))); connect(Core(), SIGNAL(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)), this, SLOT(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType))); @@ -80,6 +82,7 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexdumpWidget::selectionChanged); connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, this, &HexdumpWidget::selectionChanged); + connect(seekable, &CutterSeekableWidget::seekChanged, this, &HexdumpWidget::on_seekChanged); format = Format::Hex; initParsing(); @@ -281,7 +284,7 @@ void HexdumpWidget::refresh(RVA addr) updateHeaders(); if (addr == RVA_INVALID) { - addr = Core()->getOffset(); + addr = seekable->getOffset(); } cols = Core()->getConfigi("hex.cols"); @@ -323,7 +326,7 @@ void HexdumpWidget::refresh(RVA addr) QTextBlockFormat formatTmp = offsetCursor.blockFormat(); formatTmp.setBackground(QColor(64, 129, 160)); offsetCursor.setBlockFormat(formatTmp); - + updateWidths(); // Update other text areas scroll @@ -452,7 +455,7 @@ void HexdumpWidget::selectionChanged() int pos = asciiAddressToPosition(adr); setTextEditPosition(ui->hexASCIIText, pos); sent_seek = true; - Core()->seek(adr); + seekable->seek(adr); sent_seek = false; connectScroll(false); return; @@ -503,7 +506,7 @@ void HexdumpWidget::selectionChanged() targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor); ui->hexASCIIText->setTextCursor(targetTextCursor); sent_seek = true; - Core()->seek(startAddress); + seekable->seek(startAddress); sent_seek = false; } else { QTextCursor textCursor = ui->hexASCIIText->textCursor(); @@ -514,7 +517,7 @@ void HexdumpWidget::selectionChanged() setTextEditPosition(ui->hexHexText, pos); connectScroll(false); sent_seek = true; - Core()->seek(adr); + seekable->seek(adr); sent_seek = false; return; } @@ -534,7 +537,7 @@ void HexdumpWidget::selectionChanged() targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor); ui->hexHexText->setTextCursor(targetTextCursor); sent_seek = true; - Core()->seek(startAddress); + seekable->seek(startAddress); sent_seek = false; } @@ -570,6 +573,11 @@ void HexdumpWidget::showHexdumpContextMenu(const QPoint &pt) QMenu *formatSubmenu = menu->addMenu(tr("Format")); formatSubmenu->addAction(ui->actionFormatHex); formatSubmenu->addAction(ui->actionFormatOctal); + + menu->addSeparator(); + syncAction.setText(tr("Sync/unsync offset")); + menu->addAction(&syncAction); + // TODO: // formatSubmenu->addAction(ui->actionFormatHalfWord); // formatSubmenu->addAction(ui->actionFormatWord); @@ -595,6 +603,18 @@ void HexdumpWidget::showHexdumpContextMenu(const QPoint &pt) delete menu; } +void HexdumpWidget::toggleSync() +{ + QString windowTitle = tr("Hexdump"); + seekable->toggleSyncWithCore(); + if (seekable->getSyncWithCore()) { + setWindowTitle(windowTitle); + } else { + setWindowTitle(windowTitle + " (not synced)"); + seekable->setIndependentOffset(Core()->getOffset()); + } +} + void HexdumpWidget::showHexASCIIContextMenu(const QPoint &pt) { // Set Hex ASCII popup menu diff --git a/src/widgets/HexdumpWidget.h b/src/widgets/HexdumpWidget.h index bf4f8d30..79c2c02f 100644 --- a/src/widgets/HexdumpWidget.h +++ b/src/widgets/HexdumpWidget.h @@ -10,6 +10,7 @@ #include "Cutter.h" #include "CutterDockWidget.h" +#include "CutterSeekableWidget.h" #include "utils/Highlighter.h" #include "utils/HexAsciiHighlighter.h" #include "utils/HexHighlighter.h" @@ -27,9 +28,7 @@ class HexdumpWidget : public CutterDockWidget public: explicit HexdumpWidget(MainWindow *main, QAction *action = nullptr); ~HexdumpWidget(); - Highlighter *highlighter; - enum Format { Hex, Octal, @@ -50,6 +49,7 @@ public slots: void zoomIn(int range = 1); void zoomOut(int range = 1); + void toggleSync(); protected: virtual void resizeEvent(QResizeEvent *event) override; @@ -102,6 +102,8 @@ private: int bufferLines; int cols; + QAction syncAction; + CutterSeekableWidget *seekable; private slots: void on_seekChanged(RVA addr);