rpcs3/rpcs3/rpcs3qt/cheat_manager.cpp
Talkashie dabb2cc9a0
Fix typos, improve consistency
Fixes typos where spelling or grammar is objectively wrong.
Changes wording and capitalization in some areas to be more consistent with other areas.
2023-07-28 13:09:06 +03:00

1072 lines
29 KiB
C++

#include <QHBoxLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMessageBox>
#include <QMenu>
#include <QClipboard>
#include <QGuiApplication>
#include "cheat_manager.h"
#include "Emu/System.h"
#include "Emu/Memory/vm.h"
#include "Emu/CPU/CPUThread.h"
#include "Emu/IdManager.h"
#include "Emu/Cell/PPUAnalyser.h"
#include "Emu/Cell/PPUFunction.h"
#include "util/yaml.hpp"
#include "util/asm.hpp"
#include "util/to_endian.hpp"
#include "Utilities/File.h"
#include "Utilities/StrUtil.h"
#include "Utilities/bin_patch.h" // get_patches_path()
LOG_CHANNEL(log_cheat, "Cheat");
cheat_manager_dialog* cheat_manager_dialog::inst = nullptr;
template <>
void fmt_class_string<cheat_type>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](cheat_type value)
{
switch (value)
{
case cheat_type::unsigned_8_cheat: return "Unsigned 8 bits";
case cheat_type::unsigned_16_cheat: return "Unsigned 16 bits";
case cheat_type::unsigned_32_cheat: return "Unsigned 32 bits";
case cheat_type::unsigned_64_cheat: return "Unsigned 64 bits";
case cheat_type::signed_8_cheat: return "Signed 8 bits";
case cheat_type::signed_16_cheat: return "Signed 16 bits";
case cheat_type::signed_32_cheat: return "Signed 32 bits";
case cheat_type::signed_64_cheat: return "Signed 64 bits";
case cheat_type::max: break;
}
return unknown;
});
}
YAML::Emitter& operator<<(YAML::Emitter& out, const cheat_info& rhs)
{
std::string type_formatted;
fmt::append(type_formatted, "%s", rhs.type);
out << YAML::BeginSeq << rhs.description << type_formatted << rhs.red_script << YAML::EndSeq;
return out;
}
cheat_engine::cheat_engine()
{
const std::string patches_path = patch_engine::get_patches_path();
if (!fs::create_path(patches_path))
{
log_cheat.fatal("Failed to create path: %s (%s)", patches_path, fs::g_tls_error);
return;
}
const std::string path = patches_path + m_cheats_filename;
if (fs::file cheat_file{path, fs::read + fs::create})
{
auto [yml_cheats, error] = yaml_load(cheat_file.to_string());
if (!error.empty())
{
log_cheat.error("Error parsing %s: %s", path, error);
return;
}
for (const auto& yml_cheat : yml_cheats)
{
const std::string& game_name = yml_cheat.first.Scalar();
for (const auto& yml_offset : yml_cheat.second)
{
const u32 offset = get_yaml_node_value<u32>(yml_offset.first, error);
if (!error.empty())
{
log_cheat.error("Error parsing %s: node key %s is not a u32 offset", path, yml_offset.first.Scalar());
return;
}
cheat_info cheat = get_yaml_node_value<cheat_info>(yml_offset.second, error);
if (!error.empty())
{
log_cheat.error("Error parsing %s: node %s is not a cheat_info node", path, yml_offset.first.Scalar());
return;
}
cheat.game = game_name;
cheat.offset = offset;
cheats[game_name][offset] = std::move(cheat);
}
}
}
else
{
log_cheat.error("Error loading %s", path);
}
}
void cheat_engine::save() const
{
const std::string patches_path = patch_engine::get_patches_path();
if (!fs::create_path(patches_path))
{
log_cheat.fatal("Failed to create path: %s (%s)", patches_path, fs::g_tls_error);
return;
}
const std::string path = patches_path + m_cheats_filename;
fs::file cheat_file(path, fs::rewrite);
if (!cheat_file)
return;
YAML::Emitter out;
out << YAML::BeginMap;
for (const auto& game_entry : cheats)
{
out << game_entry.first;
out << YAML::BeginMap;
for (const auto& offset_entry : game_entry.second)
{
out << YAML::Hex << offset_entry.first;
out << offset_entry.second;
}
out << YAML::EndMap;
}
out << YAML::EndMap;
cheat_file.write(out.c_str(), out.size());
}
void cheat_engine::import_cheats_from_str(const std::string& str_cheats)
{
auto cheats_vec = fmt::split(str_cheats, {"^^^"});
for (auto& cheat_line : cheats_vec)
{
cheat_info new_cheat;
if (new_cheat.from_str(cheat_line))
cheats[new_cheat.game][new_cheat.offset] = new_cheat;
}
}
std::string cheat_engine::export_cheats_to_str() const
{
std::string cheats_str;
for (const auto& game : cheats)
{
for (const auto& offset : ::at32(cheats, game.first))
{
cheats_str += offset.second.to_str();
cheats_str += "^^^";
}
}
return cheats_str;
}
bool cheat_engine::exist(const std::string& game, const u32 offset) const
{
return cheats.contains(game) && ::at32(cheats, game).contains(offset);
}
void cheat_engine::add(const std::string& game, const std::string& description, const cheat_type type, const u32 offset, const std::string& red_script)
{
cheats[game][offset] = cheat_info{game, description, type, offset, red_script};
}
cheat_info* cheat_engine::get(const std::string& game, const u32 offset)
{
if (!exist(game, offset))
return nullptr;
return &cheats[game][offset];
}
bool cheat_engine::erase(const std::string& game, const u32 offset)
{
if (!exist(game, offset))
return false;
cheats[game].erase(offset);
return true;
}
bool cheat_engine::resolve_script(u32& final_offset, const u32 offset, const std::string& red_script)
{
enum operand
{
operand_equal,
operand_add,
operand_sub
};
auto do_operation = [](const operand op, u32& param1, const u32 param2) -> u32
{
switch (op)
{
case operand_equal: return param1 = param2;
case operand_add: return param1 += param2;
case operand_sub: return param1 -= param2;
}
return ensure(0);
};
operand cur_op = operand_equal;
u32 index = 0;
while (index < red_script.size())
{
if (std::isdigit(static_cast<u8>(red_script[index])))
{
std::string num_string;
for (; index < red_script.size(); index++)
{
if (!std::isdigit(static_cast<u8>(red_script[index])))
break;
num_string += red_script[index];
}
const u32 num_value = std::stoul(num_string);
do_operation(cur_op, final_offset, num_value);
}
else
{
switch (red_script[index])
{
case '$':
{
do_operation(cur_op, final_offset, offset);
index++;
break;
}
case '[':
{
// find corresponding ]
s32 found_close = 1;
std::string sub_script;
for (index++; index < red_script.size(); index++)
{
if (found_close == 0)
break;
if (red_script[index] == ']')
found_close--;
else if (red_script[index] == '[')
found_close++;
if (found_close != 0)
sub_script += red_script[index];
}
if (found_close)
return false;
// Resolves content of []
u32 res_addr = 0;
if (!resolve_script(res_addr, offset, sub_script))
return false;
// Tries to get value at resolved address
bool success;
const u32 res_value = get_value<u32>(res_addr, success);
if (!success)
return false;
do_operation(cur_op, final_offset, res_value);
break;
}
case '+':
cur_op = operand_add;
index++;
break;
case '-':
cur_op = operand_sub;
index++;
break;
case ' ': index++; break;
default: log_cheat.fatal("invalid character in redirection script"); return false;
}
}
}
return true;
}
template <typename T>
std::vector<u32> cheat_engine::search(const T value, const std::vector<u32>& to_filter)
{
std::vector<u32> results;
to_be_t<T> value_swapped = value;
if (Emu.IsStopped())
return {};
cpu_thread::suspend_all(nullptr, {}, [&]
{
if (!to_filter.empty())
{
for (const auto& off : to_filter)
{
if (vm::check_addr<sizeof(T)>(off))
{
if (*vm::get_super_ptr<T>(off) == value_swapped)
results.push_back(off);
}
}
}
else
{
// Looks through mapped memory
for (u32 page_start = 0x10000; page_start < 0xF0000000; page_start += 4096)
{
if (vm::check_addr(page_start))
{
// Assumes the values are aligned
for (u32 index = 0; index < 4096; index += sizeof(T))
{
if (*vm::get_super_ptr<T>(page_start + index) == value_swapped)
results.push_back(page_start + index);
}
}
}
}
});
return results;
}
template <typename T>
T cheat_engine::get_value(const u32 offset, bool& success)
{
if (Emu.IsStopped())
{
success = false;
return 0;
}
return cpu_thread::suspend_all(nullptr, {}, [&]() -> T
{
if (!vm::check_addr<sizeof(T)>(offset))
{
success = false;
return 0;
}
success = true;
return *vm::get_super_ptr<T>(offset);
});
}
template <typename T>
bool cheat_engine::set_value(const u32 offset, const T value)
{
if (Emu.IsStopped())
return false;
if (!vm::check_addr<sizeof(T)>(offset))
{
return false;
}
return cpu_thread::suspend_all(nullptr, {}, [&]
{
if (!vm::check_addr<sizeof(T)>(offset))
{
return false;
}
*vm::get_super_ptr<T>(offset) = value;
const bool exec_code_at_start = vm::check_addr(offset, vm::page_executable);
const bool exec_code_at_end = [&]()
{
if constexpr (sizeof(T) == 1)
{
return exec_code_at_start;
}
else
{
return vm::check_addr(offset + sizeof(T) - 1, vm::page_executable);
}
}();
if (exec_code_at_end || exec_code_at_start)
{
extern void ppu_register_function_at(u32, u32, ppu_intrp_func_t);
u32 addr = offset, size = sizeof(T);
if (exec_code_at_end && exec_code_at_start)
{
size = utils::align<u32>(addr + size, 4) - (addr & -4);
addr &= -4;
}
else if (exec_code_at_end)
{
size -= utils::align<u32>(size - 4096 + (addr & 4095), 4);
addr = utils::align<u32>(addr, 4096);
}
else if (exec_code_at_start)
{
size = utils::align<u32>(4096 - (addr & 4095), 4);
addr &= -4;
}
// Reinitialize executable code
ppu_register_function_at(addr, size, nullptr);
}
return true;
});
}
bool cheat_engine::is_addr_safe(const u32 offset)
{
if (Emu.IsStopped())
return false;
const auto ppum = g_fxo->try_get<main_ppu_module>();
if (!ppum)
{
log_cheat.fatal("Failed to get ppu_module");
return false;
}
std::vector<std::pair<u32, u32>> segs;
for (const auto& seg : ppum->segs)
{
if ((seg.flags & 3))
{
segs.emplace_back(seg.addr, seg.size);
}
}
if (segs.empty())
{
log_cheat.fatal("Couldn't find a +rw-x section");
return false;
}
for (const auto& seg : segs)
{
if (offset >= seg.first && offset < (seg.first + seg.second))
return true;
}
return false;
}
u32 cheat_engine::reverse_lookup(const u32 addr, const u32 max_offset, const u32 max_depth, const u32 cur_depth)
{
for (u32 index = 0; index <= max_offset; index += 4)
{
std::vector<u32> ptrs = search(addr - index, {});
log_cheat.fatal("Found %d pointer(s) for addr 0x%x [offset: %d cur_depth:%d]", ptrs.size(), addr, index, cur_depth);
for (const auto& ptr : ptrs)
{
if (is_addr_safe(ptr))
return ptr;
}
// If depth has not been reached dig deeper
if (!ptrs.empty() && cur_depth < max_depth)
{
for (const auto& ptr : ptrs)
{
const u32 result = reverse_lookup(ptr, max_offset, max_depth, cur_depth + 1);
if (result)
return result;
}
}
}
return 0;
}
enum cheat_table_columns : int
{
title = 0,
description,
type,
offset,
script
};
cheat_manager_dialog::cheat_manager_dialog(QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Cheat Manager"));
setObjectName("cheat_manager");
setMinimumSize(QSize(800, 400));
QVBoxLayout* main_layout = new QVBoxLayout();
tbl_cheats = new QTableWidget(this);
tbl_cheats->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
tbl_cheats->setSelectionBehavior(QAbstractItemView::SelectRows);
tbl_cheats->setContextMenuPolicy(Qt::CustomContextMenu);
tbl_cheats->setColumnCount(5);
tbl_cheats->setHorizontalHeaderLabels(QStringList() << tr("Game") << tr("Description") << tr("Type") << tr("Offset") << tr("Script"));
main_layout->addWidget(tbl_cheats);
QHBoxLayout* btn_layout = new QHBoxLayout();
QLabel* lbl_value_final = new QLabel(tr("Current Value:"));
edt_value_final = new QLineEdit();
btn_apply = new QPushButton(tr("Apply"), this);
btn_apply->setEnabled(false);
btn_layout->addWidget(lbl_value_final);
btn_layout->addWidget(edt_value_final);
btn_layout->addWidget(btn_apply);
main_layout->addLayout(btn_layout);
QGroupBox* grp_add_cheat = new QGroupBox(tr("Cheat Search"));
QVBoxLayout* grp_add_cheat_layout = new QVBoxLayout();
QHBoxLayout* grp_add_cheat_sub_layout = new QHBoxLayout();
QPushButton* btn_new_search = new QPushButton(tr("New Search"));
btn_new_search->setEnabled(false);
btn_filter_results = new QPushButton(tr("Filter Results"));
btn_filter_results->setEnabled(false);
edt_cheat_search_value = new QLineEdit();
cbx_cheat_search_type = new QComboBox();
for (u64 i = 0; i < cheat_type_max; i++)
{
const QString item_text = get_localized_cheat_type(static_cast<cheat_type>(i));
cbx_cheat_search_type->addItem(item_text);
}
cbx_cheat_search_type->setCurrentIndex(static_cast<u8>(cheat_type::signed_32_cheat));
grp_add_cheat_sub_layout->addWidget(btn_new_search);
grp_add_cheat_sub_layout->addWidget(btn_filter_results);
grp_add_cheat_sub_layout->addWidget(edt_cheat_search_value);
grp_add_cheat_sub_layout->addWidget(cbx_cheat_search_type);
grp_add_cheat_layout->addLayout(grp_add_cheat_sub_layout);
lst_search = new QListWidget(this);
lst_search->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
lst_search->setSelectionBehavior(QAbstractItemView::SelectRows);
lst_search->setContextMenuPolicy(Qt::CustomContextMenu);
grp_add_cheat_layout->addWidget(lst_search);
grp_add_cheat->setLayout(grp_add_cheat_layout);
main_layout->addWidget(grp_add_cheat);
setLayout(main_layout);
// Edit/Manage UI
connect(tbl_cheats, &QTableWidget::itemClicked, [this](QTableWidgetItem* item)
{
if (!item)
return;
const int row = item->row();
if (row == -1)
return;
cheat_info* cheat = g_cheat.get(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt());
if (!cheat)
{
log_cheat.fatal("Failed to retrieve cheat selected from internal cheat_engine");
return;
}
u32 final_offset;
if (!cheat->red_script.empty())
{
final_offset = 0;
if (!cheat_engine::resolve_script(final_offset, cheat->offset, cheat->red_script))
{
btn_apply->setEnabled(false);
edt_value_final->setText(tr("Failed to resolve redirection script"));
return;
}
}
else
{
final_offset = cheat->offset;
}
if (Emu.IsStopped())
{
btn_apply->setEnabled(false);
edt_value_final->setText(tr("This Application is not running"));
return;
}
bool success;
u64 result_value;
switch (cheat->type)
{
case cheat_type::unsigned_8_cheat: result_value = cheat_engine::get_value<u8>(final_offset, success); break;
case cheat_type::unsigned_16_cheat: result_value = cheat_engine::get_value<u16>(final_offset, success); break;
case cheat_type::unsigned_32_cheat: result_value = cheat_engine::get_value<u32>(final_offset, success); break;
case cheat_type::unsigned_64_cheat: result_value = cheat_engine::get_value<u64>(final_offset, success); break;
case cheat_type::signed_8_cheat: result_value = cheat_engine::get_value<s8>(final_offset, success); break;
case cheat_type::signed_16_cheat: result_value = cheat_engine::get_value<s16>(final_offset, success); break;
case cheat_type::signed_32_cheat: result_value = cheat_engine::get_value<s32>(final_offset, success); break;
case cheat_type::signed_64_cheat: result_value = cheat_engine::get_value<s64>(final_offset, success); break;
default: log_cheat.fatal("Unsupported cheat type"); return;
}
if (success)
{
if (cheat->type >= cheat_type::signed_8_cheat && cheat->type <= cheat_type::signed_64_cheat)
edt_value_final->setText(tr("%1").arg(static_cast<s64>(result_value)));
else
edt_value_final->setText(tr("%1").arg(result_value));
}
else
{
edt_value_final->setText(tr("Failed to get the value from memory"));
}
btn_apply->setEnabled(success);
});
connect(tbl_cheats, &QTableWidget::cellChanged, [this](int row, int column)
{
QTableWidgetItem* item = tbl_cheats->item(row, column);
if (!item)
{
return;
}
if (column != cheat_table_columns::description && column != cheat_table_columns::script)
{
log_cheat.fatal("A column other than description and script was edited");
return;
}
cheat_info* cheat = g_cheat.get(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt());
if (!cheat)
{
log_cheat.fatal("Failed to retrieve cheat edited from internal cheat_engine");
return;
}
switch (column)
{
case cheat_table_columns::description: cheat->description = item->text().toStdString(); break;
case cheat_table_columns::script: cheat->red_script = item->text().toStdString(); break;
default: break;
}
g_cheat.save();
});
connect(tbl_cheats, &QTableWidget::customContextMenuRequested, [this](const QPoint& loc)
{
const QPoint globalPos = tbl_cheats->mapToGlobal(loc);
QMenu* menu = new QMenu();
QAction* delete_cheats = new QAction(tr("Delete"), menu);
QAction* import_cheats = new QAction(tr("Import Cheats"));
QAction* export_cheats = new QAction(tr("Export Cheats"));
QAction* reverse_cheat = new QAction(tr("Reverse-Lookup Cheat"));
connect(delete_cheats, &QAction::triggered, [this]()
{
const auto selected = tbl_cheats->selectedItems();
std::set<int> rows;
for (const auto& sel : selected)
{
const int row = sel->row();
if (rows.count(row))
continue;
g_cheat.erase(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt());
rows.insert(row);
}
update_cheat_list();
});
connect(import_cheats, &QAction::triggered, [this]()
{
QClipboard* clipboard = QGuiApplication::clipboard();
g_cheat.import_cheats_from_str(clipboard->text().toStdString());
update_cheat_list();
});
connect(export_cheats, &QAction::triggered, [this]()
{
const auto selected = tbl_cheats->selectedItems();
std::set<int> rows;
std::string export_string;
for (const auto& sel : selected)
{
const int row = sel->row();
if (rows.count(row))
continue;
cheat_info* cheat = g_cheat.get(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt());
if (cheat)
export_string += cheat->to_str() + "^^^";
rows.insert(row);
}
QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(QString::fromStdString(export_string));
});
connect(reverse_cheat, &QAction::triggered, [this]()
{
QTableWidgetItem* item = tbl_cheats->item(tbl_cheats->currentRow(), cheat_table_columns::offset);
if (item)
{
const u32 offset = item->data(Qt::UserRole).toUInt();
const u32 result = cheat_engine::reverse_lookup(offset, 32, 12);
log_cheat.fatal("Result is 0x%x", result);
}
});
menu->addAction(delete_cheats);
menu->addSeparator();
// menu->addAction(reverse_cheat);
// menu->addSeparator();
menu->addAction(import_cheats);
menu->addAction(export_cheats);
menu->exec(globalPos);
});
connect(btn_apply, &QPushButton::clicked, [this](bool /*checked*/)
{
const int row = tbl_cheats->currentRow();
cheat_info* cheat = g_cheat.get(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt());
if (!cheat)
{
log_cheat.fatal("Failed to retrieve cheat selected from internal cheat_engine");
return;
}
std::pair<bool, bool> results;
u32 final_offset;
if (!cheat->red_script.empty())
{
final_offset = 0;
if (!g_cheat.resolve_script(final_offset, cheat->offset, cheat->red_script))
{
btn_apply->setEnabled(false);
edt_value_final->setText(tr("Failed to resolve redirection script"));
}
}
else
{
final_offset = cheat->offset;
}
// TODO: better way to do this?
switch (static_cast<cheat_type>(cbx_cheat_search_type->currentIndex()))
{
case cheat_type::unsigned_8_cheat: results = convert_and_set<u8>(final_offset); break;
case cheat_type::unsigned_16_cheat: results = convert_and_set<u16>(final_offset); break;
case cheat_type::unsigned_32_cheat: results = convert_and_set<u32>(final_offset); break;
case cheat_type::unsigned_64_cheat: results = convert_and_set<u64>(final_offset); break;
case cheat_type::signed_8_cheat: results = convert_and_set<s8>(final_offset); break;
case cheat_type::signed_16_cheat: results = convert_and_set<s16>(final_offset); break;
case cheat_type::signed_32_cheat: results = convert_and_set<s32>(final_offset); break;
case cheat_type::signed_64_cheat: results = convert_and_set<s64>(final_offset); break;
default: log_cheat.fatal("Unsupported cheat type"); return;
}
if (!results.first)
{
QMessageBox::warning(this, tr("Error converting value"), tr("Couldn't convert the value you typed to the integer type of that cheat"), QMessageBox::Ok);
return;
}
if (!results.second)
{
QMessageBox::warning(this, tr("Error applying value"), tr("Couldn't patch memory"), QMessageBox::Ok);
return;
}
});
// Search UI
connect(btn_new_search, &QPushButton::clicked, [this](bool /*checked*/)
{
offsets_found.clear();
do_the_search();
});
connect(edt_cheat_search_value, &QLineEdit::textChanged, this, [btn_new_search, this](const QString& text)
{
if (btn_new_search)
{
btn_new_search->setEnabled(!text.isEmpty());
}
if (btn_filter_results)
{
btn_filter_results->setEnabled(!text.isEmpty() && !offsets_found.empty());
}
});
connect(btn_filter_results, &QPushButton::clicked, [this](bool /*checked*/) { do_the_search(); });
connect(lst_search, &QListWidget::customContextMenuRequested, [this](const QPoint& loc)
{
const QPoint globalPos = lst_search->mapToGlobal(loc);
const int current_row = lst_search->currentRow();
QListWidgetItem* item = lst_search->item(current_row);
// Skip if the item was a placeholder
if (!item || item->data(Qt::UserRole).toBool())
return;
QMenu* menu = new QMenu();
QAction* add_to_cheat_list = new QAction(tr("Add to cheat list"), menu);
const u32 offset = offsets_found[current_row];
const cheat_type type = static_cast<cheat_type>(cbx_cheat_search_type->currentIndex());
connect(add_to_cheat_list, &QAction::triggered, [name = Emu.GetTitle(), offset, type, this]()
{
if (g_cheat.exist(name, offset))
{
if (QMessageBox::question(this, tr("Cheat already exists"), tr("Do you want to overwrite the existing cheat?"), QMessageBox::Ok | QMessageBox::Cancel) != QMessageBox::Ok)
return;
}
std::string comment;
if (!cheat_engine::is_addr_safe(offset))
comment = "Unsafe";
g_cheat.add(name, comment, type, offset, "");
update_cheat_list();
});
menu->addAction(add_to_cheat_list);
menu->exec(globalPos);
});
update_cheat_list();
}
cheat_manager_dialog::~cheat_manager_dialog()
{
inst = nullptr;
}
cheat_manager_dialog* cheat_manager_dialog::get_dlg(QWidget* parent)
{
if (inst == nullptr)
inst = new cheat_manager_dialog(parent);
return inst;
}
template <typename T>
T cheat_manager_dialog::convert_from_QString(const QString& str, bool& success)
{
T result;
if constexpr (std::is_same<T, u8>::value)
{
const u16 result_16 = str.toUShort(&success);
if (result_16 > 0xFF)
success = false;
result = static_cast<T>(result_16);
}
if constexpr (std::is_same<T, u16>::value)
result = str.toUShort(&success);
if constexpr (std::is_same<T, u32>::value)
result = str.toUInt(&success);
if constexpr (std::is_same<T, u64>::value)
result = str.toULongLong(&success);
if constexpr (std::is_same<T, s8>::value)
{
const s16 result_16 = str.toShort(&success);
if (result_16 < -128 || result_16 > 127)
success = false;
result = static_cast<T>(result_16);
}
if constexpr (std::is_same<T, s16>::value)
result = str.toShort(&success);
if constexpr (std::is_same<T, s32>::value)
result = str.toInt(&success);
if constexpr (std::is_same<T, s64>::value)
result = str.toLongLong(&success);
return result;
}
template <typename T>
bool cheat_manager_dialog::convert_and_search()
{
bool res_conv;
const QString to_search = edt_cheat_search_value->text();
T value = convert_from_QString<T>(to_search, res_conv);
if (!res_conv)
return false;
offsets_found = cheat_engine::search(value, offsets_found);
return true;
}
template <typename T>
std::pair<bool, bool> cheat_manager_dialog::convert_and_set(u32 offset)
{
bool res_conv;
const QString to_set = edt_value_final->text();
T value = convert_from_QString<T>(to_set, res_conv);
if (!res_conv)
return {false, false};
return {true, cheat_engine::set_value(offset, value)};
}
void cheat_manager_dialog::do_the_search()
{
bool res_conv = false;
// TODO: better way to do this?
switch (static_cast<cheat_type>(cbx_cheat_search_type->currentIndex()))
{
case cheat_type::unsigned_8_cheat: res_conv = convert_and_search<u8>(); break;
case cheat_type::unsigned_16_cheat: res_conv = convert_and_search<u16>(); break;
case cheat_type::unsigned_32_cheat: res_conv = convert_and_search<u32>(); break;
case cheat_type::unsigned_64_cheat: res_conv = convert_and_search<u64>(); break;
case cheat_type::signed_8_cheat: res_conv = convert_and_search<s8>(); break;
case cheat_type::signed_16_cheat: res_conv = convert_and_search<s16>(); break;
case cheat_type::signed_32_cheat: res_conv = convert_and_search<s32>(); break;
case cheat_type::signed_64_cheat: res_conv = convert_and_search<s64>(); break;
default: log_cheat.fatal("Unsupported cheat type"); break;
}
if (!res_conv)
{
QMessageBox::warning(this, tr("Error converting value"), tr("Couldn't convert the search value you typed to the integer type you selected"), QMessageBox::Ok);
return;
}
lst_search->clear();
const usz size = offsets_found.size();
if (size == 0)
{
QListWidgetItem* item = new QListWidgetItem(tr("Nothing found"));
item->setData(Qt::UserRole, true);
lst_search->insertItem(0, item);
}
else if (size > 10000)
{
// Only show entries below a fixed amount. Too many entries can take forever to render and fill up memory quickly.
QListWidgetItem* item = new QListWidgetItem(tr("Too many entries to display (%0)").arg(size));
item->setData(Qt::UserRole, true);
lst_search->insertItem(0, item);
}
else
{
for (u32 row = 0; row < size; row++)
{
lst_search->insertItem(row, tr("0x%0").arg(offsets_found[row], 1, 16).toUpper());
}
}
btn_filter_results->setEnabled(!offsets_found.empty() && edt_cheat_search_value && !edt_cheat_search_value->text().isEmpty());
}
void cheat_manager_dialog::update_cheat_list()
{
usz num_rows = 0;
for (const auto& name : g_cheat.cheats)
num_rows += name.second.size();
tbl_cheats->setRowCount(::narrow<int>(num_rows));
u32 row = 0;
{
const QSignalBlocker blocker(tbl_cheats);
for (const auto& game : g_cheat.cheats)
{
for (const auto& offset : game.second)
{
QTableWidgetItem* item_game = new QTableWidgetItem(QString::fromStdString(offset.second.game));
item_game->setFlags(item_game->flags() & ~Qt::ItemIsEditable);
tbl_cheats->setItem(row, cheat_table_columns::title, item_game);
tbl_cheats->setItem(row, cheat_table_columns::description, new QTableWidgetItem(QString::fromStdString(offset.second.description)));
std::string type_formatted;
fmt::append(type_formatted, "%s", offset.second.type);
QTableWidgetItem* item_type = new QTableWidgetItem(QString::fromStdString(type_formatted));
item_type->setFlags(item_type->flags() & ~Qt::ItemIsEditable);
tbl_cheats->setItem(row, cheat_table_columns::type, item_type);
QTableWidgetItem* item_offset = new QTableWidgetItem(tr("0x%1").arg(offset.second.offset, 1, 16).toUpper());
item_offset->setData(Qt::UserRole, QVariant(offset.second.offset));
item_offset->setFlags(item_offset->flags() & ~Qt::ItemIsEditable);
tbl_cheats->setItem(row, cheat_table_columns::offset, item_offset);
tbl_cheats->setItem(row, cheat_table_columns::script, new QTableWidgetItem(QString::fromStdString(offset.second.red_script)));
row++;
}
}
}
g_cheat.save();
}
QString cheat_manager_dialog::get_localized_cheat_type(cheat_type type)
{
switch (type)
{
case cheat_type::unsigned_8_cheat: return tr("Unsigned 8 bits");
case cheat_type::unsigned_16_cheat: return tr("Unsigned 16 bits");
case cheat_type::unsigned_32_cheat: return tr("Unsigned 32 bits");
case cheat_type::unsigned_64_cheat: return tr("Unsigned 64 bits");
case cheat_type::signed_8_cheat: return tr("Signed 8 bits");
case cheat_type::signed_16_cheat: return tr("Signed 16 bits");
case cheat_type::signed_32_cheat: return tr("Signed 32 bits");
case cheat_type::signed_64_cheat: return tr("Signed 64 bits");
case cheat_type::max: break;
}
std::string type_formatted;
fmt::append(type_formatted, "%s", type);
return QString::fromStdString(type_formatted);
}