diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d4460bf01..4e6f78253 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -19,6 +19,7 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp + cheat_gui.cpp configure_audio.cpp configure_debug.cpp configure_dialog.cpp @@ -52,6 +53,7 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h + cheat_gui.h configure_audio.h configure_debug.h configure_dialog.h @@ -71,6 +73,7 @@ set(UIS debugger/disassembler.ui debugger/profiler.ui debugger/registers.ui + cheat_gui.ui configure.ui configure_audio.ui configure_debug.ui diff --git a/src/citra_qt/cheat_gui.cpp b/src/citra_qt/cheat_gui.cpp new file mode 100644 index 000000000..0e9727285 --- /dev/null +++ b/src/citra_qt/cheat_gui.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include + +#include "citra_qt/cheat_gui.h" +#include "core/hle/kernel/process.h" +#include "ui_cheat_gui.h" + +CheatDialog::CheatDialog(QWidget* parent) + : QDialog(parent), ui(std::make_unique()) { + // Setup gui control settings + ui->setupUi(this); + setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + ui->tableCheats->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->tableCheats->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->tableCheats->setColumnWidth(0, 30); + ui->tableCheats->setColumnWidth(2, 85); + ui->tableCheats->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + ui->tableCheats->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->tableCheats->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->textDetails->setEnabled(false); + ui->textNotes->setEnabled(false); + const auto game_id = + Common::StringFromFormat("%016llX", Kernel::g_current_process->codeset->program_id); + ui->labelTitle->setText(tr("Title ID: %1").arg(QString::fromStdString(game_id))); + + connect(ui->buttonClose, &QPushButton::released, this, &CheatDialog::OnCancel); + connect(ui->buttonNewCheat, &QPushButton::released, this, &CheatDialog::OnAddCheat); + connect(ui->buttonSave, &QPushButton::released, this, &CheatDialog::OnSave); + connect(ui->buttonDelete, &QPushButton::released, this, &CheatDialog::OnDelete); + connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected); + connect(ui->textDetails, &QPlainTextEdit::textChanged, this, &CheatDialog::OnDetailsChanged); + connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnNotesChanged); + + LoadCheats(); +} + +CheatDialog::~CheatDialog() {} + +void CheatDialog::LoadCheats() { + cheats = CheatEngine::CheatEngine::ReadFileContents(); + + ui->tableCheats->setRowCount(cheats.size()); + + for (size_t i = 0; i < cheats.size(); i++) { + auto enabled = new QCheckBox(); + enabled->setChecked(cheats[i]->GetEnabled()); + enabled->setStyleSheet("margin-left:7px;"); + ui->tableCheats->setItem(i, 0, new QTableWidgetItem()); + ui->tableCheats->setCellWidget(i, 0, enabled); + ui->tableCheats->setItem( + i, 1, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetName()))); + ui->tableCheats->setItem( + i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetType()))); + enabled->setProperty("row", static_cast(i)); + + connect(enabled, &QCheckBox::stateChanged, this, &CheatDialog::OnCheckChanged); + } +} + +void CheatDialog::OnSave() { + CheatEngine::CheatEngine::Save(cheats); + CheatCore::RefreshCheats(); + close(); +} + +void CheatDialog::OnCancel() { + close(); +} + +void CheatDialog::OnRowSelected(int row, int column) { + selection_changing = true; + if (row == -1) { + ui->textNotes->setPlainText(""); + ui->textDetails->setPlainText(""); + current_row = -1; + selection_changing = false; + ui->textDetails->setEnabled(false); + ui->textNotes->setEnabled(false); + return; + } + + ui->textDetails->setEnabled(true); + ui->textNotes->setEnabled(true); + const auto& current_cheat = cheats[row]; + ui->textNotes->setPlainText( + QString::fromStdString(Common::Join(current_cheat->GetNotes(), "\n"))); + + std::vector details; + for (const auto& line : current_cheat->GetCheatLines()) + details.push_back(line.cheat_line); + ui->textDetails->setPlainText(QString::fromStdString(Common::Join(details, "\n"))); + + current_row = row; + + selection_changing = false; +} + +void CheatDialog::OnNotesChanged() { + if (selection_changing) + return; + auto notes = ui->textNotes->toPlainText(); + auto old_notes = cheats[current_row]->GetNotes(); + Common::SplitString(notes.toStdString(), '\n', old_notes); + cheats[current_row]->SetNotes(old_notes); +} + +void CheatDialog::OnDetailsChanged() { + if (selection_changing) + return; + auto details = ui->textDetails->toPlainText(); + std::vector detail_lines; + Common::SplitString(details.toStdString(), '\n', detail_lines); + auto new_lines = std::vector(); + for (const auto& line : detail_lines) { + new_lines.emplace_back(line); + } + cheats[current_row]->SetCheatLines(new_lines); +} + +void CheatDialog::OnCheckChanged(int state) { + const QCheckBox* checkbox = qobject_cast(sender()); + int row = static_cast(checkbox->property("row").toInt()); + cheats[row]->SetEnabled(state); +} + +void CheatDialog::OnDelete() { + QItemSelectionModel* selectionModel = ui->tableCheats->selectionModel(); + QModelIndexList selected = selectionModel->selectedRows(); + std::vector rows; + for (int i = selected.count() - 1; i >= 0; i--) { + QModelIndex index = selected.at(i); + int row = index.row(); + cheats.erase(cheats.begin() + row); + rows.push_back(row); + } + for (int row : rows) + ui->tableCheats->removeRow(row); + + ui->tableCheats->clearSelection(); + OnRowSelected(-1, -1); +} + +void CheatDialog::OnAddCheat() { + NewCheatDialog dialog; + dialog.exec(); + + auto result = dialog.GetReturnValue(); + if (result == nullptr) + return; + cheats.push_back(result); + int new_cheat_index = static_cast(cheats.size() - 1); + auto enabled = new QCheckBox(); + ui->tableCheats->setRowCount(cheats.size()); + enabled->setCheckState(Qt::CheckState::Unchecked); + enabled->setStyleSheet("margin-left:7px;"); + ui->tableCheats->setItem(new_cheat_index, 0, new QTableWidgetItem()); + ui->tableCheats->setCellWidget(new_cheat_index, 0, enabled); + ui->tableCheats->setItem(new_cheat_index, 1, new QTableWidgetItem(QString::fromStdString( + cheats[new_cheat_index]->GetName()))); + ui->tableCheats->setItem(new_cheat_index, 2, new QTableWidgetItem(QString::fromStdString( + cheats[new_cheat_index]->GetType()))); + enabled->setProperty("row", new_cheat_index); + connect(enabled, &QCheckBox::stateChanged, this, &CheatDialog::OnCheckChanged); + ui->tableCheats->selectRow(new_cheat_index); + OnRowSelected(new_cheat_index, 0); +} +NewCheatDialog::NewCheatDialog(QWidget* parent) : QDialog(parent) { + resize(250, 150); + setSizeGripEnabled(false); + auto mainLayout = new QVBoxLayout(this); + + QHBoxLayout* namePanel = new QHBoxLayout(); + name_block = new QLineEdit(); + QLabel* nameLabel = new QLabel(); + nameLabel->setText(tr("Name: ")); + namePanel->addWidget(nameLabel); + namePanel->addWidget(name_block); + + QHBoxLayout* typePanel = new QHBoxLayout(); + auto typeLabel = new QLabel(); + typeLabel->setText(tr("Type: ")); + type_select = new QComboBox(); + type_select->addItem(tr("Gateway"), 0); + typePanel->addWidget(typeLabel); + typePanel->addWidget(type_select); + + auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(button_box, &QDialogButtonBox::accepted, this, [=]() { + auto name = name_block->text().toStdString(); + if (type_select->currentIndex() == 0 && Common::Trim(name).length() > 0) { + return_value = + std::make_shared(name_block->text().toStdString()); + } + this->close(); + }); + connect(button_box, &QDialogButtonBox::rejected, this, [=]() { this->close(); }); + QHBoxLayout* confirmationPanel = new QHBoxLayout(); + confirmationPanel->addWidget(button_box); + mainLayout->addLayout(namePanel); + mainLayout->addLayout(typePanel); + mainLayout->addLayout(confirmationPanel); +} +NewCheatDialog::~NewCheatDialog() {} \ No newline at end of file diff --git a/src/citra_qt/cheat_gui.h b/src/citra_qt/cheat_gui.h new file mode 100644 index 000000000..158a6dafc --- /dev/null +++ b/src/citra_qt/cheat_gui.h @@ -0,0 +1,60 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "core/cheat_core.h" + +class QComboBox; +class QLineEdit; +class QWidget; +namespace Ui { +class CheatDialog; +class NewCheatDialog; +} + +class CheatDialog : public QDialog { + Q_OBJECT + +public: + explicit CheatDialog(QWidget* parent = nullptr); + ~CheatDialog(); + +private: + std::unique_ptr ui; + int current_row = -1; + bool selection_changing = false; + std::vector> cheats; + + void LoadCheats(); + +private slots: + void OnAddCheat(); + void OnSave(); + void OnCancel(); + void OnRowSelected(int row, int column); + void OnNotesChanged(); + void OnDetailsChanged(); + void OnCheckChanged(int state); + void OnDelete(); +}; + +class NewCheatDialog : public QDialog { + Q_OBJECT +public: + explicit NewCheatDialog(QWidget* parent = nullptr); + ~NewCheatDialog(); + std::shared_ptr GetReturnValue() const { + return return_value; + } + +private: + QLineEdit* name_block; + QComboBox* type_select; + std::shared_ptr return_value; +}; diff --git a/src/citra_qt/cheat_gui.ui b/src/citra_qt/cheat_gui.ui new file mode 100644 index 000000000..c1f00e842 --- /dev/null +++ b/src/citra_qt/cheat_gui.ui @@ -0,0 +1,217 @@ + + + CheatDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 862 + 612 + + + + + 0 + 0 + + + + Cheats + + + + + 10 + 10 + 300 + 31 + + + + + 10 + + + + Game Title - GameID + + + + + + 10 + 570 + 841 + 41 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Add Cheat + + + + + + + Delete + + + + + + + Save + + + + + + + Close + + + + + + + + + 10 + 80 + 551 + 471 + + + + + + + false + + + 3 + + + true + + + false + + + + + + + + + Name + + + + + Type + + + + + + + + + + 10 + 60 + 121 + 16 + + + + Available Cheats: + + + + + + 580 + 440 + 271 + 111 + + + + + + + + + + + + 580 + 420 + 111 + 16 + + + + Notes: + + + + + + 580 + 80 + 271 + 311 + + + + + + + + + + + + 580 + 60 + 55 + 16 + + + + Details: + + + + + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index f765c0147..da69b478d 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -14,6 +14,7 @@ #include #include #include "citra_qt/bootmanager.h" +#include "citra_qt/cheat_gui.h" #include "citra_qt/config.h" #include "citra_qt/configure_dialog.h" #include "citra_qt/debugger/callstack.h" @@ -209,6 +210,7 @@ void GMainWindow::RestoreUIState() { microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); microProfileDialog->setVisible(UISettings::values.microprofile_visible); #endif + ui.action_Cheats->setEnabled(false); game_list->LoadInterfaceLayout(); @@ -225,6 +227,7 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection); connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); + connect(ui.action_Cheats, SIGNAL(triggered()), this, SLOT(OnCheats())); connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()), Qt::DirectConnection); connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); @@ -405,6 +408,7 @@ void GMainWindow::ShutdownGame() { ui.action_Start->setText(tr("Start")); ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(false); + ui.action_Cheats->setEnabled(false); render_window->hide(); game_list->show(); @@ -517,6 +521,7 @@ void GMainWindow::OnStartGame() { ui.action_Start->setEnabled(false); ui.action_Start->setText(tr("Continue")); + ui.action_Cheats->setEnabled(true); ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); @@ -574,6 +579,11 @@ void GMainWindow::OnSwapScreens() { Settings::Apply(); } +void GMainWindow::OnCheats() { + CheatDialog cheat_dialog(this); + cheat_dialog.exec(); +} + void GMainWindow::OnCreateGraphicsSurfaceViewer() { auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index a2fd45c47..abe00c151 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -126,6 +126,7 @@ private slots: void OnMenuRecentFile(); void OnSwapScreens(); void OnConfigure(); + void OnCheats(); void OnDisplayTitleBars(bool); void ToggleWindowMode(); void OnCreateGraphicsSurfaceViewer(); diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index adfa3689e..f009088df 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -74,6 +74,7 @@ + @@ -151,6 +152,11 @@ Configure... + + + Cheats ... + + true diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index bad311793..a297caef4 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -462,4 +462,43 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l return std::string(buffer, len); } + +std::string TrimLeft(const std::string& str, const std::string& delimiters = " \f\n\r\t\v") { + const auto pos = str.find_first_not_of(delimiters); + if (pos == std::string::npos) + return {}; + + return str.substr(pos); +} + +std::string TrimRight(const std::string& str, const std::string delimiters = " \f\n\r\t\v") { + const auto pos = str.find_last_not_of(delimiters); + if (pos == std::string::npos) + return {}; + + return str.substr(0, pos + 1); +} + +std::string Trim(const std::string& str, const std::string delimiters) { + return TrimLeft(TrimRight(str, delimiters), delimiters); +} + +std::string Join(const std::vector& elements, const char* const separator) { + switch (elements.size()) { + case 0: + return ""; + case 1: + return elements[0]; + default: + std::ostringstream os; + std::copy(elements.begin(), elements.end(), + std::ostream_iterator(os, separator)); + + // Drop the trailing delimiter. + std::string result = os.str(); + result.pop_back(); + + return result; + } +} } diff --git a/src/common/string_util.h b/src/common/string_util.h index 075bf4ecb..e5d6cbf24 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -4,9 +4,13 @@ #pragma once +#include +#include #include #include +#include #include +#include #include #include #include @@ -134,4 +138,8 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { * NUL-terminated then the string ends at max_len characters. */ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); + +std::string Trim(const std::string& str, const std::string delimiters = " \f\n\r\t\v"); + +std::string Join(const std::vector& elements, const char* const separator); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 408d3a0cb..0323c0670 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -13,6 +13,7 @@ set(SRCS arm/skyeye_common/vfp/vfpdouble.cpp arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpsingle.cpp + cheat_core.cpp core.cpp core_timing.cpp file_sys/archive_backend.cpp @@ -189,6 +190,7 @@ set(HEADERS arm/skyeye_common/vfp/asm_vfp.h arm/skyeye_common/vfp/vfp.h arm/skyeye_common/vfp/vfp_helper.h + cheat_core.h core.h core_timing.h file_sys/archive_backend.h diff --git a/src/core/cheat_core.cpp b/src/core/cheat_core.cpp new file mode 100644 index 000000000..c8c2d2be8 --- /dev/null +++ b/src/core/cheat_core.cpp @@ -0,0 +1,401 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/cheat_core.h" +#include "core/hle/kernel/process.h" +#include "core/memory.h" + +namespace CheatCore { +constexpr u64 frame_ticks = 268123480ull / 60; +static int tick_event; +static std::unique_ptr cheat_engine; + +static void CheatTickCallback(u64, int cycles_late) { + if (cheat_engine == nullptr) + cheat_engine = std::make_unique(); + cheat_engine->Run(); + CoreTiming::ScheduleEvent(frame_ticks - cycles_late, tick_event); +} + +void Init() { + tick_event = CoreTiming::RegisterEvent("CheatCore::tick_event", CheatTickCallback); + CoreTiming::ScheduleEvent(frame_ticks, tick_event); +} + +void Shutdown() { + CoreTiming::UnscheduleEvent(tick_event, 0); +} + +void RefreshCheats() { + cheat_engine->RefreshCheats(); +} +} // namespace CheatCore + +namespace CheatEngine { +static std::string GetFilePath() { + const auto program_id = + Common::StringFromFormat("%016llX", Kernel::g_current_process->codeset->program_id); + return FileUtil::GetUserPath(D_USER_IDX) + "cheats" + DIR_SEP + program_id + ".txt"; +} +CheatEngine::CheatEngine() { + const auto file_path = GetFilePath(); + if (!FileUtil::Exists(file_path)) + FileUtil::CreateEmptyFile(file_path); + cheats_list = ReadFileContents(); +} + +std::vector> CheatEngine::ReadFileContents() { + std::string file_path = GetFilePath(); + + std::string contents; + FileUtil::ReadFileToString(true, file_path.c_str(), contents); + std::vector lines; + Common::SplitString(contents, '\n', lines); + + std::string code_type = + "Gateway"; // If more cheat types added, need to specify which type in parsing. + std::vector notes; + std::vector cheat_lines; + std::vector> cheats; + std::string name; + bool enabled = false; + for (size_t i = 0; i < lines.size(); i++) { + std::string current_line = std::string(lines[i].c_str()); + current_line = Common::Trim(current_line); + if (current_line.compare(0, 2, "+[") == 0) { // Enabled code + if (!cheat_lines.empty()) { + if (code_type == "Gateway") + cheats.push_back( + std::make_shared(cheat_lines, notes, enabled, name)); + } + name = current_line.substr(2, current_line.length() - 3); + cheat_lines.clear(); + notes.clear(); + enabled = true; + continue; + } else if (current_line.front() == '[') { // Disabled code + if (!cheat_lines.empty()) { + if (code_type == "Gateway") + cheats.push_back( + std::make_shared(cheat_lines, notes, enabled, name)); + } + name = current_line.substr(1, current_line.length() - 2); + cheat_lines.clear(); + notes.clear(); + enabled = false; + continue; + } else if (current_line.front() == '*') { // Comment + notes.push_back(std::move(current_line)); + } else if (!current_line.empty()) { + cheat_lines.emplace_back(std::move(current_line)); + } + if (i == lines.size() - 1) { // End of file + if (!cheat_lines.empty()) { + if (code_type == "Gateway") + cheats.push_back( + std::make_shared(cheat_lines, notes, enabled, name)); + } + } + } + return cheats; +} + +void CheatEngine::Save(std::vector> cheats) { + const auto file_path = GetFilePath(); + FileUtil::IOFile file = FileUtil::IOFile(file_path, "w+"); + for (auto& cheat : cheats) { + if (cheat->GetType() == "Gateway") { + auto cheatString = cheat->ToString(); + file.WriteBytes(cheatString.c_str(), cheatString.length()); + } + } +} + +void CheatEngine::RefreshCheats() { + const auto file_path = GetFilePath(); + if (!FileUtil::Exists(file_path)) + FileUtil::CreateEmptyFile(file_path); + cheats_list = ReadFileContents(); +} + +void CheatEngine::Run() { + for (auto& cheat : cheats_list) { + cheat->Execute(); + } +} + +void GatewayCheat::Execute() { + if (enabled == false) + return; + u32 addr = 0; + u32 reg = 0; + u32 offset = 0; + u32 val = 0; + int if_flag = 0; + int loop_count = 0; + s32 loopbackline = 0; + u32 counter = 0; + bool loop_flag = false; + for (size_t i = 0; i < cheat_lines.size(); i++) { + auto line = cheat_lines[i]; + if (line.type == -1) + continue; + addr = line.address; + val = line.value; + if (if_flag > 0) { + if (line.type == 0x0E) + i += (line.value + 7) / 8; + if ((line.type == 0x0D) && (line.sub_type == 0)) + if_flag--; // ENDIF + if ((line.type == 0x0D) && (line.sub_type == 2)) // NEXT & Flush + { + if (loop_flag) + i = loopbackline - 1; + else { + offset = 0; + reg = 0; + loop_count = 0; + counter = 0; + if_flag = 0; + loop_flag = 0; + } + } + continue; + } + + switch (line.type) { + case 0x00: { // 0XXXXXXX YYYYYYYY word[XXXXXXX+offset] = YYYYYYYY + addr = line.address + offset; + Memory::Write32(addr, val); + break; + } + case 0x01: { // 1XXXXXXX 0000YYYY half[XXXXXXX+offset] = YYYY + addr = line.address + offset; + Memory::Write16(addr, static_cast(val)); + break; + } + case 0x02: { // 2XXXXXXX 000000YY byte[XXXXXXX+offset] = YY + addr = line.address + offset; + Memory::Write8(addr, static_cast(val)); + break; + } + case 0x03: { // 3XXXXXXX YYYYYYYY IF YYYYYYYY > word[XXXXXXX] ;unsigned + if (line.address == 0) + line.address = offset; + val = Memory::Read32(line.address); + if (line.value > val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x04: { // 4XXXXXXX YYYYYYYY IF YYYYYYYY < word[XXXXXXX] ;unsigned + if (line.address == 0) + line.address = offset; + val = Memory::Read32(line.address); + if (line.value < val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x05: { // 5XXXXXXX YYYYYYYY IF YYYYYYYY = word[XXXXXXX] + if (line.address == 0) + line.address = offset; + val = Memory::Read32(line.address); + if (line.value == val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x06: { // 6XXXXXXX YYYYYYYY IF YYYYYYYY <> word[XXXXXXX] + if (line.address == 0) + line.address = offset; + val = Memory::Read32(line.address); + if (line.value != val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x07: { // 7XXXXXXX ZZZZYYYY IF YYYY > ((not ZZZZ) AND half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + val = Memory::Read16(line.address); + if (line.value > val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x08: { // 8XXXXXXX ZZZZYYYY IF YYYY < ((not ZZZZ) AND half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + val = Memory::Read16(line.address); + if (static_cast(line.value) < val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x09: { // 9XXXXXXX ZZZZYYYY IF YYYY = ((not ZZZZ) AND half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + val = Memory::Read16(line.address); + if (static_cast(line.value) == val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x0A: { // AXXXXXXX ZZZZYYYY IF YYYY <> ((not ZZZZ) AND half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + val = Memory::Read16(line.address); + if (static_cast(line.value) != val) { + if (if_flag > 0) + if_flag--; + } else { + if_flag++; + } + break; + } + case 0x0B: { // BXXXXXXX 00000000 offset = word[XXXXXXX+offset] + addr = line.address + offset; + offset = Memory::Read32(addr); + break; + } + case 0x0C: { + if (loop_count < (line.value + 1)) + loop_flag = 1; + else + loop_flag = 0; + loop_count++; + loopbackline = i; + break; + } + case 0x0D: { + switch (line.sub_type) { + case 0x00: + break; + case 0x01: { + if (loop_flag) + i = loopbackline - 1; + break; + } + case 0x02: { + if (loop_flag) + i = loopbackline - 1; + else { + offset = 0; + reg = 0; + loop_count = 0; + counter = 0; + if_flag = 0; + loop_flag = 0; + } + break; + } + case 0x03: { + offset = line.value; + break; + } + case 0x04: { + reg += line.value; + break; + } + case 0x05: { + reg = line.value; + break; + } + case 0x06: { + addr = line.value + offset; + Memory::Write32(addr, reg); + offset += 4; + break; + } + case 0x07: { + addr = line.value + offset; + Memory::Write16(addr, static_cast(reg)); + offset += 2; + break; + } + case 0x08: { + addr = line.value + offset; + Memory::Write8(addr, static_cast(reg)); + offset += 1; + break; + } + case 0x09: { + addr = line.value + offset; + reg = Memory::Read32(addr); + break; + } + case 0x0A: { + addr = line.value + offset; + reg = Memory::Read16(addr); + break; + } + case 0x0B: { + addr = line.value + offset; + reg = Memory::Read8(addr); + break; + } + case 0x0C: { + offset += line.value; + break; + } + case 0x0D: { + // TODO: Implement Joker codes + break; + } + } + } + case 0x0E: { // EXXXXXXX YYYYYYYY Copy YYYYYYYY parameter bytes to [XXXXXXXX+offset...] + // TODO: Implement whatever this is... + break; + } + } + } +} + +std::string GatewayCheat::ToString() { + std::string result; + if (cheat_lines.empty()) + return result; + if (enabled) + result += '+'; + result += '[' + name + "]\n"; + for (auto& str : notes) { + if (str.front() == '*') + str.insert(0, 1, '*'); + } + result += Common::Join(notes, "\n"); + if (!notes.empty()) + result += '\n'; + for (const auto& line : cheat_lines) + result += line.cheat_line + '\n'; + result += '\n'; + return result; +} +} diff --git a/src/core/cheat_core.h b/src/core/cheat_core.h new file mode 100644 index 000000000..8e987c009 --- /dev/null +++ b/src/core/cheat_core.h @@ -0,0 +1,135 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/string_util.h" +#include "core/core_timing.h" + +namespace CheatCore { +/* + * Starting point for running cheat codes. Initializes tick event and executes cheats every tick. + */ +void Init(); +void Shutdown(); +void RefreshCheats(); +} +namespace CheatEngine { +/* + * Represents a single line of a cheat, i.e. 1xxxxxxxx yyyyyyyy + */ +struct CheatLine { + explicit CheatLine(std::string line) { + line = std::string(line.c_str()); // remove '/0' characters if any. + line = Common::Trim(line); + constexpr int cheat_length = 17; + if (line.length() != cheat_length) { + type = -1; + cheat_line = line; + return; + } + try { + type = std::stoi(line.substr(0, 1), 0, 16); + // 0xD types have extra subtype value, i.e. 0xDA + if (type == 0xD) + sub_type = std::stoi(line.substr(1, 1), 0, 16); + address = std::stoi(line.substr(1, 8), 0, 16); + value = std::stoi(line.substr(10, 8), 0, 16); + cheat_line = line; + } catch (std::exception e) { + type = -1; + cheat_line = line; + return; + } + } + int type; + int sub_type; + u32 address; + u32 value; + std::string cheat_line; +}; + +/* + * Base Interface for all types of cheats. + */ +class CheatBase { +public: + virtual void Execute() = 0; + virtual ~CheatBase() = default; + virtual std::string ToString() = 0; + const std::vector& GetNotes() const { + return notes; + } + void SetNotes(std::vector new_notes) { + notes = std::move(new_notes); + } + bool GetEnabled() const { + return enabled; + } + void SetEnabled(bool enabled_) { + enabled = enabled_; + } + const std::string& GetType() const { + return type; + } + void SetType(std::string new_type) { + type = std::move(new_type); + } + const std::vector& GetCheatLines() const { + return cheat_lines; + } + void SetCheatLines(std::vector new_lines) { + cheat_lines = std::move(new_lines); + } + const std::string& GetName() const { + return name; + } + void SetName(std::string new_name) { + name = std::move(new_name); + } + +protected: + std::vector notes; + bool enabled = false; + std::string type; + std::vector cheat_lines; + std::string name; +}; +/* + * Implements support for Gateway (GateShark) cheats. + */ +class GatewayCheat : public CheatBase { +public: + GatewayCheat(std::string name_) { + name = std::move(name_); + type = "Gateway"; + } + GatewayCheat(std::vector cheat_lines_, std::vector notes_, + bool enabled_, std::string name_) + : GatewayCheat{std::move(name_)} { + cheat_lines = std::move(cheat_lines_); + notes = std::move(notes_); + enabled = enabled_; + }; + void Execute() override; + std::string ToString() override; +}; + +/* + * Handles loading/saving of cheats and executing them. + */ +class CheatEngine { +public: + CheatEngine(); + void Run(); + static std::vector> ReadFileContents(); + static void Save(std::vector> cheats); + void RefreshCheats(); + +private: + std::vector> cheats_list; +}; +} diff --git a/src/core/core.cpp b/src/core/core.cpp index 202cd332b..67bc3a2c9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -9,6 +9,7 @@ #include "core/arm/arm_interface.h" #include "core/arm/dynarmic/arm_dynarmic.h" #include "core/arm/dyncom/arm_dyncom.h" +#include "core/cheat_core.h" #include "core/core.h" #include "core/core_timing.h" #include "core/gdbstub/gdbstub.h" @@ -140,6 +141,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { Kernel::Init(system_mode); Service::Init(); AudioCore::Init(); + CheatCore::Init(); GDBStub::Init(); if (!VideoCore::Init(emu_window)) { @@ -153,6 +155,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { void System::Shutdown() { GDBStub::Shutdown(); + CheatCore::Shutdown(); AudioCore::Shutdown(); VideoCore::Shutdown(); Service::Shutdown();