Skip to content

Commit

Permalink
add ability to edit strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Auto committed May 5, 2024
1 parent b5abeff commit 317cb50
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ x64dbg_plugin(${PROJECT_NAME}
include/QtHelpers/WidgetSpelunkyRooms.h
include/QtHelpers/DialogEditSimpleValue.h
include/QtHelpers/DialogEditState.h
include/QtHelpers/DialogEditString.h
include/QtHelpers/TableWidgetItemNumeric.h
include/QtHelpers/TreeWidgetItemNumeric.h
include/QtHelpers/ItemModelVirtualTable.h
Expand Down Expand Up @@ -136,6 +137,7 @@ x64dbg_plugin(${PROJECT_NAME}
src/QtHelpers/WidgetSpelunkyRooms.cpp
src/QtHelpers/DialogEditSimpleValue.cpp
src/QtHelpers/DialogEditState.cpp
src/QtHelpers/DialogEditString.cpp
src/QtHelpers/ItemModelVirtualTable.cpp
src/QtHelpers/ItemModelVirtualFunctions.cpp
src/QtHelpers/SortFilterProxyModelStringsTable.cpp
Expand Down
3 changes: 2 additions & 1 deletion include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace S2Plugin
* [[ Roles explanation: ]]
* The first 5 roles are all saved to the name field
* those are used as information about the row
* memory address in the name field are used just for row update and should not be used for anything else
* memory address in the name field are used just for row update and shouldn't really be used for anything else
*
* value, comparison value, memoryaddress and delta fields all should contain the `gsRoleRawValue` data
* (may differ with some special types)
Expand Down Expand Up @@ -155,6 +155,7 @@ namespace S2Plugin
std::string firstParameterType;
std::string secondParameterType;
std::string comment;
// size in bytes
size_t get_size() const;

// For checking duplicate names
Expand Down
35 changes: 35 additions & 0 deletions include/QtHelpers/DialogEditString.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <QDialog>
#include <QLineEdit>
#include <QSize>
#include <QString>
#include <QWidget>
#include <cstdint>

namespace S2Plugin
{
enum class MemoryFieldType;

class DialogEditString : public QDialog
{
Q_OBJECT

public:
// size without the last char (null termination)
DialogEditString(const QString& fieldName, QString initialValue, uintptr_t memoryAddress, int size, MemoryFieldType type, QWidget* parent = nullptr);

protected:
QSize minimumSizeHint() const override;
QSize sizeHint() const override;

private slots:
void cancelBtnClicked();
void changeBtnClicked();

private:
QLineEdit* mLineEdit;
uintptr_t mMemoryAddress;
MemoryFieldType mFieldType;
};
} // namespace S2Plugin
2 changes: 0 additions & 2 deletions include/Spelunky2.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ namespace S2Plugin
{
constexpr uint32_t TEB_offset = 0x120;

class EntityDB;

struct Spelunky2
{
static Spelunky2* get();
Expand Down
2 changes: 1 addition & 1 deletion src/Data/StringsTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

QString S2Plugin::StringsTable::stringForIndex(uint32_t idx) const
{
if (count() < idx)
if (count() <= idx)
{
return QString("INVALID OR NOT APPLICABLE");
}
Expand Down
94 changes: 94 additions & 0 deletions src/QtHelpers/DialogEditString.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "QtHelpers/DialogEditString.h"
#include "Configuration.h"
#include "QtPlugin.h"
#include "pluginmain.h"
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <string>

S2Plugin::DialogEditString::DialogEditString(const QString& fieldName, QString value, uintptr_t memoryAddress, int size, MemoryFieldType type, QWidget* parent)
: QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint), mMemoryAddress(memoryAddress), mFieldType(type)
{
setModal(true);
setWindowTitle("Change value");
setWindowIcon(getCavemanIcon());
auto layout = new QVBoxLayout(this);

// FIELDS
auto gridLayout = new QGridLayout();

gridLayout->addWidget(new QLabel(QString("Change value of %1").arg(fieldName), this), 0, 0, 1, 2);
gridLayout->addWidget(new QLabel("New text:", this), 1, 0);

mLineEdit = new QLineEdit(this);
mLineEdit->setMaxLength(size);
mLineEdit->setText(value);
gridLayout->addWidget(mLineEdit, 1, 1);

// BUTTONS
auto buttonLayout = new QHBoxLayout();

auto cancelBtn = new QPushButton("Cancel", this);
QObject::connect(cancelBtn, &QPushButton::clicked, this, &DialogEditString::cancelBtnClicked);
cancelBtn->setAutoDefault(false);
auto changeBtn = new QPushButton("Change", this);
QObject::connect(changeBtn, &QPushButton::clicked, this, &DialogEditString::changeBtnClicked);
changeBtn->setAutoDefault(true);

buttonLayout->addStretch();
buttonLayout->addWidget(cancelBtn);
buttonLayout->addWidget(changeBtn);

layout->addLayout(gridLayout);
layout->addStretch();
layout->addLayout(buttonLayout);

mLineEdit->setFocus();
mLineEdit->selectAll();
}

QSize S2Plugin::DialogEditString::minimumSizeHint() const
{

return QSize(350, 150);
}

QSize S2Plugin::DialogEditString::sizeHint() const
{
return minimumSizeHint();
}

void S2Plugin::DialogEditString::cancelBtnClicked()
{
reject();
}

void S2Plugin::DialogEditString::changeBtnClicked()
{
switch (mFieldType)
{
case MemoryFieldType::UTF16Char:
{
ushort v = mLineEdit->text().isEmpty() ? 0 : mLineEdit->text()[0].unicode();
Script::Memory::WriteWord(mMemoryAddress, v);
break;
}
case MemoryFieldType::UTF16StringFixedSize:
{
auto v = mLineEdit->text().toStdWString();
Script::Memory::Write(mMemoryAddress, v.data(), (v.size() + 1) * 2, nullptr); // +1 to include null character
break;
}
case MemoryFieldType::ConstCharPointer:
case MemoryFieldType::UTF8StringFixedSize:
{
auto v = mLineEdit->text().toStdString();
Script::Memory::Write(mMemoryAddress, v.data(), v.size() + 1, nullptr); // +1 to include null character
break;
}
}
accept();
}
72 changes: 55 additions & 17 deletions src/QtHelpers/TreeViewMemoryFields.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "Data/TextureDB.h"
#include "QtHelpers/DialogEditSimpleValue.h"
#include "QtHelpers/DialogEditState.h"
#include "QtHelpers/DialogEditString.h"
#include "QtHelpers/StyledItemDelegateHTML.h"
#include "QtPlugin.h"
#include "Spelunky2.h"
Expand Down Expand Up @@ -887,12 +888,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional<uintptr_t>
}
case MemoryFieldType::Flag: // can't be pointer, always have parent
{
constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role)
{
auto mod = idx.model();
auto parentIndex = idx.parent();
return mod->data(mod->index(idx.row(), col, parentIndex), role);
};
constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) { return idx.sibling(idx.row(), col).data(role); };

// [Known Issue]: null memory address is not handled
auto flagIndex = itemField->data(gsRoleFlagIndex).toUInt();
uint mask = (1 << (flagIndex - 1));
Expand Down Expand Up @@ -1913,12 +1910,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional<uintptr_t>
void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index)
{
auto clickedItem = mModel->itemFromIndex(index);
constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role)
{
auto mod = idx.model();
auto parentIndex = idx.parent();
return mod->data(mod->index(idx.row(), col, parentIndex), role);
};
constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) { return idx.sibling(idx.row(), col).data(role); };

switch (index.column())
{
Expand Down Expand Up @@ -2179,7 +2171,6 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index)
case MemoryFieldType::UnsignedQword:
case MemoryFieldType::Float:
case MemoryFieldType::Double:
case MemoryFieldType::UTF16Char:
case MemoryFieldType::StringsTableID:
{
auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong();
Expand Down Expand Up @@ -2215,14 +2206,61 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index)
{
bool isPointer = getDataFrom(index, gsColField, gsRoleIsPointer).toBool();
if (isPointer)
}
case MemoryFieldType::UTF16Char:
{
auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (offset != 0)
{
auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString();
QChar c = static_cast<ushort>(clickedItem->data(gsRoleRawValue).toUInt());
auto dialog = new DialogEditString(fieldName, c, offset, 1, dataType, this);
dialog->exec();
}
break;
}
case MemoryFieldType::UTF16StringFixedSize:
{
auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (offset != 0)
{
auto addr = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (addr != 0)
int size = getDataFrom(index, gsColField, gsRoleSize).toInt();
auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString();
auto stringData = std::make_unique<ushort[]>(size);
Script::Memory::Read(offset, stringData.get(), size, nullptr);
auto s = QString::fromUtf16(stringData.get());
auto dialog = new DialogEditString(fieldName, s, offset, size / 2 - 1, dataType, this);
dialog->exec();
}
break;
}
case MemoryFieldType::ConstCharPointer:
{
auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (offset != 0)
{
GuiDumpAt(addr);
GuiShowCpu();
auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString();
auto s = QString::fromStdString(ReadConstString(offset));
// [Known Issue]: Now way to safely determinate allowed lenght, so we just allow as much characters as there is already
auto dialog = new DialogEditString(fieldName, s, offset, s.length(), dataType, this);
dialog->exec();
}
break;
}
case MemoryFieldType::UTF8StringFixedSize:
{
auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (offset != 0)
{
int size = getDataFrom(index, gsColField, gsRoleSize).toInt();
auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString();
auto stringData = std::make_unique<char[]>(size);
Script::Memory::Read(offset, stringData.get(), size, nullptr);
auto s = QString::fromUtf8(stringData.get());
auto dialog = new DialogEditString(fieldName, s, offset, size - 1, dataType, this);
dialog->exec();
}
break;
}
}
emit memoryFieldValueUpdated(index.row(), clickedItem->parent());
Expand Down

0 comments on commit 317cb50

Please sign in to comment.