Implement some of w? commands in HexWidget (#1854)

* Implement write commands in hex widget 
* Add cmdRawAt and seekSilent
* Document patching context menu

Co-authored-by: itayc0hen
Co-authored-by: Kārlis Seņko
This commit is contained in:
optizone 2020-03-19 12:36:36 +03:00 committed by GitHub
parent 971a0c7a77
commit e50eddb8cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1157 additions and 72 deletions

View File

@ -0,0 +1,63 @@
Patching
==============================
Write String
----------------------------------------
**Description:** Write ASCII string at the current location. If multiple bytes are selected, the string will be written from the start of the selection. Please note, this item won't write a null-terminated nor wide strings. Please see the other items for this features.
**Steps:** Right click on a byte and select ``Edit -> Write string``
Write Length and String
----------------------------------------
**Description:** Write a length prefix followed by ASCII string at the current location. If multiple bytes are selected, the string will be written from the start of the selection. Please note, although similar to BSTR in its concept, this item would not add a null terminator WCHAR at the end of the string.
**Steps:** Right click on a byte and select ``Edit -> Write length and string``
Write Wide String
----------------------------------------
**Description:** Write null-terminated wide string at the current location. If multiple bytes are selected, the string will be written from the start of the selection.
**Steps:** Right click on a byte and select ``Edit -> Write wide string``
Write Null-Terminated String
----------------------------------------
**Description:** Write a null-terminated ASCII string at the current location. If multiple bytes are selected, the string will be written from the start of the selection.
**Steps:** Right click on a byte and select ``Edit -> Write zero terminated string``
Write Encoded\\Decoded Base64 String
----------------------------------------
**Description:** Write an encoded or decoded base64 string at the current location. If multiple bytes are selected, the string will be written from the start of the selection.
**Steps:** Right click on a byte and select ``Edit -> Write De\Encoded Base64 string`` . On the dialog that will open choose whether you want to encode a string or decode one.
Write Zeroes
----------------------------------------
**Description:** Write null-bytes at the current location. The number of null-bytes is specified by the user. If multiple bytes are selected, the null-bytes will be written from the start of the selection.
**Steps:** Right click on a byte and select ``Edit -> Write zeroes``. On the dialog that will open, specify how many null-bytes you'd like to write.
Write Random Bytes
----------------------------------------
**Description:** Write random bytes at the current location. The number of bytes is specified by the user. If multiple bytes are selected, the null-bytes will be written from the start of the selection.
**Steps:** Right click on a byte and select ``Edit -> Write random bytes``. On the dialog that will open, specify how many bytes you'd like to write.
Duplicate Bytes From Offset
----------------------------------------
**Description:** Duplicate N bytes from an offset to the current location. The number of bytes to duplicated and the offset of origin are specified by the user. If multiple bytes are selected, the bytes will be written from the start of the selection. A preview pane will display the bytes to be copied.
**Steps:** Right click on a byte and select ``Edit -> Duplicate from offset``. On the dialog that will open, specify the offset from which to copy, and how many bytes to copy.
Increment/Decrement bytes
----------------------------------------
**Description:** Increment or decrement Byte, Word, Dword or Qword at the current location. The value to add or subtract from the location is specified by the user. If multiple bytes are selected, the function will apply on the start of the selection.
**Steps:** Right click on a byte and select ``Edit -> Increment/Decrement``. On the dialog that will open, specify if you'd like to modify a Byte, Word, Dword or Qword, choose the value of the operation, and choose whether you want to increment or decrement.

View File

@ -1,5 +1,10 @@
Hexdump Widget Context Menu Hexdump Widget Context Menu
============================== ==============================
.. toctree::
:maxdepth: 1
:glob:
hexdump-context-menu/*
Bytes Per Row Bytes Per Row
---------------------------------------- ----------------------------------------

@ -1 +1 @@
Subproject commit e7f940d27b3b4eb2738afef78a6ea09ed770318c Subproject commit 4c684f1971ae64862523a5a06273f16cd61f3065

View File

@ -262,6 +262,7 @@ SOURCES += \
Main.cpp \ Main.cpp \
core/Cutter.cpp \ core/Cutter.cpp \
dialogs/EditStringDialog.cpp \ dialogs/EditStringDialog.cpp \
dialogs/WriteCommandsDialogs.cpp \
widgets/DisassemblerGraphView.cpp \ widgets/DisassemblerGraphView.cpp \
widgets/OverviewView.cpp \ widgets/OverviewView.cpp \
common/RichTextPainter.cpp \ common/RichTextPainter.cpp \
@ -389,7 +390,8 @@ SOURCES += \
common/AddressableItemModel.cpp \ common/AddressableItemModel.cpp \
widgets/ListDockWidget.cpp \ widgets/ListDockWidget.cpp \
dialogs/MultitypeFileSaveDialog.cpp \ dialogs/MultitypeFileSaveDialog.cpp \
widgets/BoolToggleDelegate.cpp widgets/BoolToggleDelegate.cpp \
common/IOModesController.cpp
GRAPHVIZ_SOURCES = \ GRAPHVIZ_SOURCES = \
widgets/GraphvizLayout.cpp widgets/GraphvizLayout.cpp
@ -399,6 +401,7 @@ HEADERS += \
core/CutterCommon.h \ core/CutterCommon.h \
core/CutterDescriptions.h \ core/CutterDescriptions.h \
dialogs/EditStringDialog.h \ dialogs/EditStringDialog.h \
dialogs/WriteCommandsDialogs.h \
widgets/DisassemblerGraphView.h \ widgets/DisassemblerGraphView.h \
widgets/OverviewView.h \ widgets/OverviewView.h \
common/RichTextPainter.h \ common/RichTextPainter.h \
@ -533,13 +536,17 @@ HEADERS += \
widgets/ListDockWidget.h \ widgets/ListDockWidget.h \
widgets/AddressableItemList.h \ widgets/AddressableItemList.h \
dialogs/MultitypeFileSaveDialog.h \ dialogs/MultitypeFileSaveDialog.h \
widgets/BoolToggleDelegate.h widgets/BoolToggleDelegate.h \
common/IOModesController.h
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h
FORMS += \ FORMS += \
dialogs/AboutDialog.ui \ dialogs/AboutDialog.ui \
dialogs/EditStringDialog.ui \ dialogs/EditStringDialog.ui \
dialogs/Base64EnDecodedWriteDialog.ui \
dialogs/DuplicateFromOffsetDialog.ui \
dialogs/IncrementDecrementDialog.ui \
dialogs/preferences/AsmOptionsWidget.ui \ dialogs/preferences/AsmOptionsWidget.ui \
dialogs/CommentsDialog.ui \ dialogs/CommentsDialog.ui \
dialogs/EditInstructionDialog.ui \ dialogs/EditInstructionDialog.ui \

View File

@ -0,0 +1,43 @@
#include "IOModesController.h"
#include "Cutter.h"
#include <QPushButton>
#include <QObject>
bool IOModesController::canWrite()
{
return Core()->isIOCacheEnabled() || Core()->isWriteModeEnabled();
}
bool IOModesController::prepareForWriting()
{
if (canWrite()) {
return true;
}
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Icon::Critical);
msgBox.setWindowTitle(QObject::tr("Write error"));
msgBox.setText(
QObject::tr("Your file is opened in read-only mode. "
"Editing is only available when the file is opened in either Write or Cache modes.\n\n"
"WARNING: In Write mode, any changes will be committed to the file on disk. "
"For safety, please consider using Cache mode and then commit the changes manually "
"via File -> Commit modifications to disk."));
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
QAbstractButton *reopenButton = msgBox.addButton(QObject::tr("Reopen in Write mode"),
QMessageBox::YesRole);
QAbstractButton *iocacheButton = msgBox.addButton(QObject::tr("Enable Cache mode"),
QMessageBox::YesRole);
msgBox.exec();
if (msgBox.clickedButton() == reopenButton) {
Core()->setWriteMode(true);
} else if (msgBox.clickedButton() == iocacheButton) {
Core()->setIOCache(true);
} else {
return false;
}
return true;
}

View File

@ -0,0 +1,15 @@
#ifndef IOMODESCONTROLLER_H
#define IOMODESCONTROLLER_H
#include "core/Cutter.h"
class IOModesController
{
public:
bool prepareForWriting();
bool canWrite();
};
#endif // IOMODESCONTROLLER_H

View File

@ -16,6 +16,7 @@
#include "core/Cutter.h" #include "core/Cutter.h"
#include "Decompiler.h" #include "Decompiler.h"
#include "r_asm.h" #include "r_asm.h"
#include "r_cmd.h"
#include "sdb.h" #include "sdb.h"
Q_GLOBAL_STATIC(CutterCore, uniqueInstance) Q_GLOBAL_STATIC(CutterCore, uniqueInstance)
@ -390,6 +391,32 @@ bool CutterCore::asyncCmd(const char *str, QSharedPointer<R2Task> &task)
return true; return true;
} }
// TODO: Refactor cmdRaw based on this implementation and use it inside cmdRawAt
QString CutterCore::cmdRawAt(const char *cmd, RVA address)
{
QString res;
RVA oldOffset = getOffset();
seekSilent(address);
{
CORE_LOCK();
r_cons_push ();
// r_cmd_call does not return the output of the command
r_cmd_call(core->rcmd, cmd);
// we grab the output straight from r_cons
res = r_cons_get_buffer();
// cleaning up
r_cons_pop ();
r_cons_echo (NULL);
}
seekSilent(oldOffset);
return res;
}
QString CutterCore::cmdRaw(const QString &str) QString CutterCore::cmdRaw(const QString &str)
{ {
QString cmdStr = str; QString cmdStr = str;
@ -771,6 +798,15 @@ void CutterCore::applyStructureOffset(const QString &structureOffset, RVA offset
emit instructionChanged(offset); emit instructionChanged(offset);
} }
void CutterCore::seekSilent(ut64 offset)
{
CORE_LOCK();
if (offset == RVA_INVALID) {
return;
}
r_core_seek(core, offset, true);
}
void CutterCore::seek(ut64 offset) void CutterCore::seek(ut64 offset)
{ {
// Slower than using the API, but the API is not complete // Slower than using the API, but the API is not complete
@ -2437,7 +2473,7 @@ QList<FunctionDescription> CutterCore::getAllFunctions()
function.nbbs = r_list_length (fcn->bbs); function.nbbs = r_list_length (fcn->bbs);
function.calltype = fcn->cc ? QString::fromUtf8(fcn->cc) : QString(); function.calltype = fcn->cc ? QString::fromUtf8(fcn->cc) : QString();
function.name = fcn->name ? QString::fromUtf8(fcn->name) : QString(); function.name = fcn->name ? QString::fromUtf8(fcn->name) : QString();
function.edges = r_anal_fcn_count_edges(fcn, nullptr); function.edges = r_anal_function_count_edges(fcn, nullptr);
function.stackframe = fcn->maxstack; function.stackframe = fcn->maxstack;
funcList.append(function); funcList.append(function);
} }
@ -3678,6 +3714,58 @@ BasicInstructionHighlighter* CutterCore::getBIHighlighter()
return &biHighlighter; return &biHighlighter;
} }
void CutterCore::setIOCache(bool enabled)
{
setConfig("io.cache", enabled);
this->iocache = enabled;
emit ioCacheChanged(enabled);
}
bool CutterCore::isIOCacheEnabled() const
{
return iocache;
}
void CutterCore::commitWriteCache()
{
if (!isWriteModeEnabled()) {
setWriteMode (true);
cmd("wci");
cmd("oo");
} else {
cmd("wci");
}
}
// Enable or disable write-mode. Avoid unecessary changes if not need.
void CutterCore::setWriteMode(bool enabled)
{
bool writeModeState = isWriteModeEnabled();
if (writeModeState == enabled) {
// New mode is the same as current. Do nothing.
return;
}
// Change from read-only to write-mode
if (enabled && !writeModeState) {
cmd("oo+");
// Change from write-mode to read-only
} else {
cmd("oo");
}
writeModeChanged (enabled);
}
bool CutterCore::isWriteModeEnabled()
{
using namespace std;
QJsonArray ans = cmdj("oj").array();
return find_if(begin(ans), end(ans), [](const QJsonValue &v) {
return v.toObject().value("raised").toBool();
})->toObject().value("writable").toBool();
}
/** /**
* @brief get a compact disassembly preview for tooltips * @brief get a compact disassembly preview for tooltips
* @param address - the address from which to print the disassembly * @param address - the address from which to print the disassembly
@ -3752,4 +3840,3 @@ QByteArray CutterCore::ioRead(RVA addr, int len)
return array; return array;
} }

View File

@ -73,6 +73,22 @@ public:
bool asyncCmd(const char *str, QSharedPointer<R2Task> &task); bool asyncCmd(const char *str, QSharedPointer<R2Task> &task);
bool asyncCmd(const QString &str, QSharedPointer<R2Task> &task) { return asyncCmd(str.toUtf8().constData(), task); } bool asyncCmd(const QString &str, QSharedPointer<R2Task> &task) { return asyncCmd(str.toUtf8().constData(), task); }
QString cmdRaw(const QString &str); QString cmdRaw(const QString &str);
/**
* @brief Execute a command \a cmd at \a address. The function will preform a silent seek to the address
* without triggering the seekChanged event nor adding new entries to the seek history. By nature, the
* API is executing raw commands, and thus ignores multiple commands and overcome command injections.
* @param cmd - a raw command to execute. If multiple commands will be passed (e.g "px 5; pd 7 && pdf") then
* only the first command will be executed.
* @param address - an address to which Cutter will temporarily seek.
* @return the output of the command
*/
QString cmdRawAt(const char *cmd, RVA address);
/**
* @brief a wrapper around cmdRawAt(const char *cmd, RVA address).
*/
QString cmdRawAt(const QString &str, RVA address) { return cmdRawAt(str.toUtf8().constData(), address); }
QJsonDocument cmdj(const char *str); QJsonDocument cmdj(const char *str);
QJsonDocument cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); } QJsonDocument cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); }
QStringList cmdList(const char *str) { return cmd(str).split(QLatin1Char('\n'), QString::SkipEmptyParts); } QStringList cmdList(const char *str) { return cmd(str).split(QLatin1Char('\n'), QString::SkipEmptyParts); }
@ -221,6 +237,8 @@ public:
/* Seek functions */ /* Seek functions */
void seek(QString thing); void seek(QString thing);
void seek(ut64 offset); void seek(ut64 offset);
void seekSilent(ut64 offset);
void seekSilent(QString thing) { seekSilent(math(thing)); }
void seekPrev(); void seekPrev();
void seekNext(); void seekNext();
void updateSeek(); void updateSeek();
@ -552,6 +570,37 @@ public:
BasicBlockHighlighter *getBBHighlighter(); BasicBlockHighlighter *getBBHighlighter();
BasicInstructionHighlighter *getBIHighlighter(); BasicInstructionHighlighter *getBIHighlighter();
/**
* @brief Enable or dsiable Cache mode. Cache mode is used to imagine writing to the opened file
* without committing the changes to the disk.
* @param enabled
*/
void setIOCache(bool enabled);
/**
* @brief Check if Cache mode is enabled.
* @return true if Cache is enabled, otherwise return false.
*/
bool isIOCacheEnabled() const;
/**
* @brief Commit write cache to the file on disk.
*/
void commitWriteCache();
/**
* @brief Enable or disable Write mode. When the file is opened in write mode, any changes to it will be immediately
* committed to the file on disk, thus modify the file. This function wrap radare2 function which re-open the file with
* the desired permissions.
* @param enabled
*/
void setWriteMode(bool enabled);
/**
* @brief Check if the file is opened in write mode.
* @return true if write mode is enabled, otherwise return false.
*/
bool isWriteModeEnabled();
signals: signals:
void refreshAll(); void refreshAll();
@ -587,6 +636,9 @@ signals:
void projectSaved(bool successfully, const QString &name); void projectSaved(bool successfully, const QString &name);
void ioCacheChanged(bool newval);
void writeModeChanged(bool newval);
/** /**
* emitted when debugTask started or finished running * emitted when debugTask started or finished running
*/ */
@ -635,6 +687,7 @@ private:
bool emptyGraph = false; bool emptyGraph = false;
BasicBlockHighlighter *bbHighlighter; BasicBlockHighlighter *bbHighlighter;
bool iocache = false;
BasicInstructionHighlighter biHighlighter; BasicInstructionHighlighter biHighlighter;
QSharedPointer<R2Task> debugTask; QSharedPointer<R2Task> debugTask;

View File

@ -139,6 +139,11 @@ void MainWindow::initUI()
connect(ui->actionExtraGraph, &QAction::triggered, this, &MainWindow::addExtraGraph); connect(ui->actionExtraGraph, &QAction::triggered, this, &MainWindow::addExtraGraph);
connect(ui->actionExtraDisassembly, &QAction::triggered, this, &MainWindow::addExtraDisassembly); connect(ui->actionExtraDisassembly, &QAction::triggered, this, &MainWindow::addExtraDisassembly);
connect(ui->actionExtraHexdump, &QAction::triggered, this, &MainWindow::addExtraHexdump); connect(ui->actionExtraHexdump, &QAction::triggered, this, &MainWindow::addExtraHexdump);
connect(ui->actionCommitChanges, &QAction::triggered, this, [this]() {
Core()->commitWriteCache();
});
ui->actionCommitChanges->setEnabled(false);
connect(Core(), &CutterCore::ioCacheChanged, ui->actionCommitChanges, &QAction::setEnabled);
widgetTypeToConstructorMap.insert(GraphWidget::getWidgetType(), getNewInstance<GraphWidget>); widgetTypeToConstructorMap.insert(GraphWidget::getWidgetType(), getNewInstance<GraphWidget>);
widgetTypeToConstructorMap.insert(DisassemblyWidget::getWidgetType(), getNewInstance<DisassemblyWidget>); widgetTypeToConstructorMap.insert(DisassemblyWidget::getWidgetType(), getNewInstance<DisassemblyWidget>);
@ -621,6 +626,24 @@ void MainWindow::setFilename(const QString &fn)
void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::closeEvent(QCloseEvent *event)
{ {
// Check if there are uncommitted changes
if (core->isIOCacheEnabled() && !core->cmdj("wcj").array().isEmpty()) {
QMessageBox::StandardButton ret = QMessageBox::question(this, APPNAME,
tr("It seems that you have changes or patches that are not committed to the file.\n"
"Do you want to commit them now?"),
(QMessageBox::StandardButtons)(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel));
if (ret == QMessageBox::Cancel) {
event->ignore();
return;
}
if (ret == QMessageBox::Save) {
core->commitWriteCache();
}
}
QMessageBox::StandardButton ret = QMessageBox::question(this, APPNAME, QMessageBox::StandardButton ret = QMessageBox::question(this, APPNAME,
tr("Do you really want to exit?\nSave your project before closing!"), tr("Do you really want to exit?\nSave your project before closing!"),
(QMessageBox::StandardButtons)(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel)); (QMessageBox::StandardButtons)(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel));

View File

@ -39,7 +39,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1013</width> <width>1013</width>
<height>22</height> <height>29</height>
</rect> </rect>
</property> </property>
<property name="defaultUp"> <property name="defaultUp">
@ -51,10 +51,10 @@
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>378</x> <x>328</x>
<y>100</y> <y>99</y>
<width>156</width> <width>265</width>
<height>220</height> <height>364</height>
</rect> </rect>
</property> </property>
<property name="title"> <property name="title">
@ -65,6 +65,8 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionImportPDB"/> <addaction name="actionImportPDB"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionCommitChanges"/>
<addaction name="separator"/>
<addaction name="actionSave"/> <addaction name="actionSave"/>
<addaction name="actionSaveAs"/> <addaction name="actionSaveAs"/>
<addaction name="actionExport_as_code"/> <addaction name="actionExport_as_code"/>
@ -1119,6 +1121,11 @@
<enum>Qt::ApplicationShortcut</enum> <enum>Qt::ApplicationShortcut</enum>
</property> </property>
</action> </action>
<action name="actionCommitChanges">
<property name="text">
<string>Commit changes</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<resources> <resources>

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Base64EnDecodedWriteDialog</class>
<widget class="QDialog" name="Base64EnDecodedWriteDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>535</width>
<height>217</height>
</rect>
</property>
<property name="windowTitle">
<string>Base64 Encode/Decode</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>String:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="base64LineEdit"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="decodeRB">
<property name="text">
<string>Decode</string>
</property>
<attribute name="buttonGroup">
<string notr="true">modeButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="encodeRB">
<property name="text">
<string>Encode</string>
</property>
<attribute name="buttonGroup">
<string notr="true">modeButtonGroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Base64EnDecodedWriteDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>229</x>
<y>356</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Base64EnDecodedWriteDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>297</x>
<y>362</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="modeButtonGroup"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DuplicateFromOffsetDialog</class>
<widget class="QDialog" name="DuplicateFromOffsetDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>289</width>
<height>323</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Duplicate from offset</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Offset:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="offsetLE">
<property name="inputMask">
<string notr="true"/>
</property>
<property name="maxLength">
<number>16</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>N bytes:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="nBytesSB">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999999999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</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>269</width>
<height>208</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="bytesLabel">
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DuplicateFromOffsetDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DuplicateFromOffsetDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IncrementDecrementDialog</class>
<widget class="QDialog" name="IncrementDecrementDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>241</width>
<height>258</height>
</rect>
</property>
<property name="windowTitle">
<string>Increment/Decrement</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Interpret as</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="nBytesCB"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="valueLabel">
<property name="text">
<string>Value:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="valueLE">
<property name="inputMask">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QRadioButton" name="incrementRB">
<property name="text">
<string>Increment</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="decrementRB">
<property name="text">
<string>Decrement</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>IncrementDecrementDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>IncrementDecrementDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,93 @@
#include "WriteCommandsDialogs.h"
#include "ui_Base64EnDecodedWriteDialog.h"
#include "ui_IncrementDecrementDialog.h"
#include "ui_DuplicateFromOffsetDialog.h"
#include "Cutter.h"
#include <cmath>
#include <QFontDatabase>
Base64EnDecodedWriteDialog::Base64EnDecodedWriteDialog(QWidget* parent) : QDialog(parent),
ui(new Ui::Base64EnDecodedWriteDialog)
{
ui->setupUi(this);
ui->decodeRB->click();
}
Base64EnDecodedWriteDialog::Mode Base64EnDecodedWriteDialog::getMode() const
{
return ui->decodeRB->isChecked() ? Decode : Encode;
}
QByteArray Base64EnDecodedWriteDialog::getData() const
{
return ui->base64LineEdit->text().toUtf8();
}
IncrementDecrementDialog::IncrementDecrementDialog(QWidget* parent) : QDialog(parent),
ui(new Ui::IncrementDecrementDialog)
{
ui->setupUi(this);
ui->incrementRB->click();
ui->nBytesCB->addItems(QStringList()
<< tr("Byte")
<< tr("Word")
<< tr("Dword")
<< tr("Qword"));
ui->valueLE->setValidator(new QRegularExpressionValidator(QRegularExpression("[0-9a-fA-Fx]{1,18}"), ui->valueLE));
}
IncrementDecrementDialog::Mode IncrementDecrementDialog::getMode() const
{
return ui->incrementRB->isChecked() ? Increase : Decrease;
}
uint8_t IncrementDecrementDialog::getNBytes() const
{
// Shift left to keep index powered by two
// This is used to create the w1, w2, w4 and w8 commands based on the selected index.
return static_cast<uint8_t>(1 << ui->nBytesCB->currentIndex());
}
uint64_t IncrementDecrementDialog::getValue() const
{
return Core()->math(ui->valueLE->text());
}
DuplicateFromOffsetDialog::DuplicateFromOffsetDialog(QWidget* parent) : QDialog(parent),
ui(new Ui::DuplicateFromOffsetDialog)
{
ui->setupUi(this);
ui->bytesLabel->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
ui->offsetLE->setValidator(new QRegularExpressionValidator(QRegularExpression("[0-9a-fA-Fx]{1,18}"), ui->offsetLE));
connect(ui->offsetLE, &QLineEdit::textChanged, this, &DuplicateFromOffsetDialog::refresh);
connect(ui->nBytesSB, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &DuplicateFromOffsetDialog::refresh);
}
RVA DuplicateFromOffsetDialog::getOffset() const
{
return Core()->math(ui->offsetLE->text());
}
size_t DuplicateFromOffsetDialog::getNBytes() const
{
return static_cast<size_t>(ui->nBytesSB->value());
}
void DuplicateFromOffsetDialog::refresh()
{
RVA offestFrom = getOffset();
QSignalBlocker sb(Core());
// Add space every two characters for word wrap in hex sequence
QRegularExpression re{"(.{2})"};
QString bytes = Core()->cmd(QString("p8 %1 @ %2")
.arg(QString::number(getNBytes()))
.arg(QString::number(offestFrom)))
.replace(re, "\\1 ");
ui->bytesLabel->setText(bytes.trimmed());
}

View File

@ -0,0 +1,55 @@
#ifndef WRITECOMMANDSDIALOGS_H
#define WRITECOMMANDSDIALOGS_H
#include <QDialog>
#include "CutterCommon.h"
namespace Ui {
class Base64EnDecodedWriteDialog;
class IncrementDecrementDialog;
class DuplicateFromOffsetDialog;
}
class Base64EnDecodedWriteDialog : public QDialog
{
Q_OBJECT
public:
explicit Base64EnDecodedWriteDialog(QWidget *parent = nullptr);
enum Mode { Encode, Decode };
Mode getMode() const;
QByteArray getData() const;
private:
Ui::Base64EnDecodedWriteDialog* ui;
};
class IncrementDecrementDialog : public QDialog
{
Q_OBJECT
public:
explicit IncrementDecrementDialog(QWidget *parent = nullptr);
enum Mode { Increase, Decrease };
Mode getMode() const;
uint8_t getNBytes() const;
uint64_t getValue() const;
private:
Ui::IncrementDecrementDialog* ui;
};
class DuplicateFromOffsetDialog : public QDialog
{
Q_OBJECT
public:
explicit DuplicateFromOffsetDialog(QWidget *parent = nullptr);
RVA getOffset() const;
size_t getNBytes() const;
private:
Ui::DuplicateFromOffsetDialog* ui;
private slots:
void refresh();
};
#endif // WRITECOMMANDSDIALOGS_H

View File

@ -654,6 +654,9 @@ QKeySequence DisassemblyContextMenu::getUndefineFunctionSequence() const
void DisassemblyContextMenu::on_actionEditInstruction_triggered() void DisassemblyContextMenu::on_actionEditInstruction_triggered()
{ {
if (!ioModesController.prepareForWriting()) {
return;
}
EditInstructionDialog e(EDIT_TEXT, this); EditInstructionDialog e(EDIT_TEXT, this);
e.setWindowTitle(tr("Edit Instruction at %1").arg(RAddressString(offset))); e.setWindowTitle(tr("Edit Instruction at %1").arg(RAddressString(offset)));
@ -666,30 +669,16 @@ void DisassemblyContextMenu::on_actionEditInstruction_triggered()
QString userInstructionOpcode = e.getInstruction(); QString userInstructionOpcode = e.getInstruction();
if (userInstructionOpcode != oldInstructionOpcode) { if (userInstructionOpcode != oldInstructionOpcode) {
Core()->editInstruction(offset, userInstructionOpcode); Core()->editInstruction(offset, userInstructionOpcode);
// Check if the write failed
auto newInstructionBytes = Core()->getInstructionBytes(offset);
if (newInstructionBytes == oldInstructionBytes) {
if (!writeFailed()) {
Core()->editInstruction(offset, userInstructionOpcode);
}
}
} }
} }
} }
void DisassemblyContextMenu::on_actionNopInstruction_triggered() void DisassemblyContextMenu::on_actionNopInstruction_triggered()
{ {
QString oldBytes = Core()->getInstructionBytes(offset); if (!ioModesController.prepareForWriting()) {
return;
Core()->nopInstruction(offset);
QString newBytes = Core()->getInstructionBytes(offset);
if (oldBytes == newBytes) {
if (!writeFailed()) {
Core()->nopInstruction(offset);
}
} }
Core()->nopInstruction(offset);
} }
void DisassemblyContextMenu::showReverseJmpQuery() void DisassemblyContextMenu::showReverseJmpQuery()
@ -711,20 +700,17 @@ void DisassemblyContextMenu::showReverseJmpQuery()
void DisassemblyContextMenu::on_actionJmpReverse_triggered() void DisassemblyContextMenu::on_actionJmpReverse_triggered()
{ {
QString oldBytes = Core()->getInstructionBytes(offset); if (!ioModesController.prepareForWriting()) {
return;
Core()->jmpReverse(offset);
QString newBytes = Core()->getInstructionBytes(offset);
if (oldBytes == newBytes) {
if (!writeFailed()) {
Core()->jmpReverse(offset);
}
} }
Core()->jmpReverse(offset);
} }
void DisassemblyContextMenu::on_actionEditBytes_triggered() void DisassemblyContextMenu::on_actionEditBytes_triggered()
{ {
if (!ioModesController.prepareForWriting()) {
return;
}
EditInstructionDialog e(EDIT_BYTES, this); EditInstructionDialog e(EDIT_BYTES, this);
e.setWindowTitle(tr("Edit Bytes at %1").arg(RAddressString(offset))); e.setWindowTitle(tr("Edit Bytes at %1").arg(RAddressString(offset)));
@ -735,37 +721,9 @@ void DisassemblyContextMenu::on_actionEditBytes_triggered()
QString bytes = e.getInstruction(); QString bytes = e.getInstruction();
if (bytes != oldBytes) { if (bytes != oldBytes) {
Core()->editBytes(offset, bytes); Core()->editBytes(offset, bytes);
QString newBytes = Core()->getInstructionBytes(offset);
if (oldBytes == newBytes) {
if (!writeFailed()) {
Core()->editBytes(offset, bytes);
} }
} }
} }
}
}
bool DisassemblyContextMenu::writeFailed()
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Icon::Critical);
msgBox.setWindowTitle(tr("Write error"));
msgBox.setText(
tr("Unable to complete write operation. Consider opening in write mode. \n\nWARNING: In write mode any changes will be committed to disk"));
msgBox.addButton(tr("OK"), QMessageBox::NoRole);
QAbstractButton *reopenButton = msgBox.addButton(tr("Reopen in write mode and try again"),
QMessageBox::YesRole);
msgBox.exec();
if (msgBox.clickedButton() == reopenButton) {
Core()->cmd("oo+");
return false;
}
return true;
}
void DisassemblyContextMenu::on_actionCopy_triggered() void DisassemblyContextMenu::on_actionCopy_triggered()
{ {

View File

@ -2,6 +2,7 @@
#define DISASSEMBLYCONTEXTMENU_H #define DISASSEMBLYCONTEXTMENU_H
#include "core/Cutter.h" #include "core/Cutter.h"
#include "common/IOModesController.h"
#include <QMenu> #include <QMenu>
#include <QKeySequence> #include <QKeySequence>
@ -35,7 +36,6 @@ private slots:
void on_actionJmpReverse_triggered(); void on_actionJmpReverse_triggered();
void on_actionEditBytes_triggered(); void on_actionEditBytes_triggered();
void showReverseJmpQuery(); void showReverseJmpQuery();
bool writeFailed();
void on_actionCopy_triggered(); void on_actionCopy_triggered();
void on_actionCopyAddr_triggered(); void on_actionCopyAddr_triggered();
@ -108,6 +108,7 @@ private:
bool canCopy; bool canCopy;
QString curHighlightedWord; // The current highlighted word QString curHighlightedWord; // The current highlighted word
MainWindow *mainWindow; MainWindow *mainWindow;
IOModesController ioModesController;
QList<QAction *> anonymousActions; QList<QAction *> anonymousActions;

View File

@ -1,6 +1,7 @@
#include "HexWidget.h" #include "HexWidget.h"
#include "Cutter.h" #include "Cutter.h"
#include "Configuration.h" #include "Configuration.h"
#include "dialogs/WriteCommandsDialogs.h"
#include <QPainter> #include <QPainter>
#include <QPaintEvent> #include <QPaintEvent>
@ -13,10 +14,15 @@
#include <QMenu> #include <QMenu>
#include <QClipboard> #include <QClipboard>
#include <QApplication> #include <QApplication>
#include <QInputDialog>
#include <QPushButton>
#include <QJsonObject>
#include <QJsonArray>
#include <QRegularExpression>
static const uint64_t MAX_COPY_SIZE = 128 * 1024 * 1024; static constexpr uint64_t MAX_COPY_SIZE = 128 * 1024 * 1024;
static const int MAX_LINE_WIDTH_PRESET = 32; static constexpr int MAX_LINE_WIDTH_PRESET = 32;
static const int MAX_LINE_WIDTH_BYTES = 128 * 1024; static constexpr int MAX_LINE_WIDTH_BYTES = 128 * 1024;
HexWidget::HexWidget(QWidget *parent) : HexWidget::HexWidget(QWidget *parent) :
QScrollArea(parent), QScrollArea(parent),
@ -114,6 +120,44 @@ HexWidget::HexWidget(QWidget *parent) :
addAction(actionSelectRange); addAction(actionSelectRange);
connect(&rangeDialog, &QDialog::accepted, this, &HexWidget::onRangeDialogAccepted); connect(&rangeDialog, &QDialog::accepted, this, &HexWidget::onRangeDialogAccepted);
actionsWriteString.reserve(5);
QAction* actionWriteString = new QAction(tr("Write string"), this);
connect(actionWriteString, &QAction::triggered, this, &HexWidget::w_writeString);
actionsWriteString.append(actionWriteString);
QAction* actionWriteLenString = new QAction(tr("Write length and string"), this);
connect(actionWriteLenString, &QAction::triggered, this, &HexWidget::w_writePascalString);
actionsWriteString.append(actionWriteLenString);
QAction* actionWriteWideString = new QAction(tr("Write wide string"), this);
connect(actionWriteWideString, &QAction::triggered, this, &HexWidget::w_writeWideString);
actionsWriteString.append(actionWriteWideString);
QAction* actionWriteCString = new QAction(tr("Write zero terminated string"), this);
connect(actionWriteCString, &QAction::triggered, this, &HexWidget::w_writeCString);
actionsWriteString.append(actionWriteCString);
QAction* actionWrite64 = new QAction(tr("Write 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);
connect(this, &HexWidget::selectionChanged, this, [this](Selection selection) { connect(this, &HexWidget::selectionChanged, this, [this](Selection selection) {
actionCopy->setEnabled(!selection.empty); actionCopy->setEnabled(!selection.empty);
}); });
@ -570,6 +614,10 @@ void HexWidget::contextMenuEvent(QContextMenuEvent *event)
menu->addMenu(rowSizeMenu); menu->addMenu(rowSizeMenu);
menu->addAction(actionHexPairs); menu->addAction(actionHexPairs);
menu->addAction(actionItemBigEndian); menu->addAction(actionItemBigEndian);
QMenu *writeMenu = menu->addMenu(tr("Edit"));
writeMenu->addActions(actionsWriteString);
writeMenu->addSeparator();
writeMenu->addActions(actionsWriteOther);
menu->addSeparator(); menu->addSeparator();
menu->addAction(actionCopy); menu->addAction(actionCopy);
disableOutsideSelectionActions(mouseOutsideSelection); disableOutsideSelectionActions(mouseOutsideSelection);
@ -606,11 +654,14 @@ void HexWidget::copy()
return; return;
QClipboard *clipboard = QApplication::clipboard(); QClipboard *clipboard = QApplication::clipboard();
QString range = QString("%1@0x%2").arg(selection.size()).arg(selection.start(), 0, 16);
if (cursorOnAscii) { if (cursorOnAscii) {
clipboard->setText(Core()->cmd("psx " + range)); clipboard->setText(Core()->cmdRawAt(QString("psx %1")
.arg(selection.size()),
selection.start()).trimmed());
} else { } else {
clipboard->setText(Core()->cmd("p8 " + range)); //TODO: copy in the format shown clipboard->setText(Core()->cmdRawAt(QString("p8 %1")
.arg(selection.size()),
selection.start()).trimmed()); //TODO: copy in the format shown
} }
} }
@ -633,6 +684,189 @@ void HexWidget::onRangeDialogAccepted()
selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress()); selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress());
} }
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();
}
}
void HexWidget::updateItemLength() void HexWidget::updateItemLength()
{ {
itemPrefixLen = 0; itemPrefixLen = 0;
@ -1305,3 +1539,7 @@ QRectF HexWidget::asciiRectangle(uint offset)
return QRectF(p, QSizeF(charWidth, lineHeight)); return QRectF(p, QSizeF(charWidth, lineHeight));
} }
RVA HexWidget::getLocationAddress() {
return !selection.isEmpty() ? selection.start() : cursor.address;
}

View File

@ -3,6 +3,8 @@
#include "Cutter.h" #include "Cutter.h"
#include "dialogs/HexdumpRangeDialog.h" #include "dialogs/HexdumpRangeDialog.h"
#include "common/IOModesController.h"
#include <QScrollArea> #include <QScrollArea>
#include <QTimer> #include <QTimer>
#include <QMenu> #include <QMenu>
@ -295,6 +297,17 @@ private slots:
void copyAddress(); void copyAddress();
void onRangeDialogAccepted(); void onRangeDialogAccepted();
// Write command slots
void w_writeString();
void w_increaseDecrease();
void w_writeZeros();
void w_write64();
void w_writeRandom();
void w_duplFromOffset();
void w_writePascalString();
void w_writeWideString();
void w_writeCString();
private: private:
void updateItemLength(); void updateItemLength();
void updateCounts(); void updateCounts();
@ -316,6 +329,12 @@ private:
QVariant readItem(int offset, QColor *color = nullptr); QVariant readItem(int offset, QColor *color = nullptr);
QString renderItem(int offset, QColor *color = nullptr); QString renderItem(int offset, QColor *color = nullptr);
QChar renderAscii(int offset, QColor *color = nullptr); QChar renderAscii(int offset, QColor *color = nullptr);
/**
* @brief Get the location on which operations such as Writing should apply.
* @return Start of selection if multiple bytes are selected. Otherwise, the curren seek of the widget.
*/
RVA getLocationAddress();
void fetchData(); void fetchData();
/** /**
* @brief Convert mouse position to address. * @brief Convert mouse position to address.
@ -483,9 +502,13 @@ private:
QAction *actionCopy; QAction *actionCopy;
QAction *actionCopyAddress; QAction *actionCopyAddress;
QAction *actionSelectRange; QAction *actionSelectRange;
QList<QAction *> actionsWriteString;
QList<QAction *> actionsWriteOther;
std::unique_ptr<AbstractData> oldData; std::unique_ptr<AbstractData> oldData;
std::unique_ptr<AbstractData> data; std::unique_ptr<AbstractData> data;
IOModesController ioModesController;
}; };
#endif // HEXWIDGET_H #endif // HEXWIDGET_H

View File

@ -84,7 +84,7 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) :
this->ui->hexTextView->addAction(&syncAction); this->ui->hexTextView->addAction(&syncAction);
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated())); connect(Config(), &Configuration::fontsUpdated, this, &HexdumpWidget::fontsUpdated);
connect(Core(), &CutterCore::refreshAll, this, [this]() { refresh(); }); connect(Core(), &CutterCore::refreshAll, this, [this]() { refresh(); });
connect(Core(), &CutterCore::instructionChanged, this, [this]() { refresh(); }); connect(Core(), &CutterCore::instructionChanged, this, [this]() { refresh(); });
connect(Core(), &CutterCore::stackChanged, this, [this]() { refresh(); }); connect(Core(), &CutterCore::stackChanged, this, [this]() { refresh(); });