diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index add7566c2..7331659a5 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp + cheat_gui.cpp game_list.cpp hotkeys.cpp main.cpp @@ -58,6 +59,7 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h + cheat_gui.h game_list.h game_list_p.h hotkeys.h @@ -74,6 +76,7 @@ set(UIS configuration/configure_input.ui configuration/configure_system.ui configuration/configure_web.ui + cheat_gui.ui debugger/registers.ui hotkeys.ui main.ui diff --git a/src/citra_qt/cheat_gui.cpp b/src/citra_qt/cheat_gui.cpp new file mode 100644 index 000000000..603d2b424 --- /dev/null +++ b/src/citra_qt/cheat_gui.cpp @@ -0,0 +1,211 @@ +#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 + setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint); + setSizeGripEnabled(false); + ui->setupUi(this); + setFixedSize(size()); + 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)); + + ui->tableCheats->setRowHeight(i, 23); + 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()))); + ui->tableCheats->setRowHeight(new_cheat_index, 23); + 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() {} diff --git a/src/citra_qt/cheat_gui.h b/src/citra_qt/cheat_gui.h new file mode 100644 index 000000000..03cb3df8a --- /dev/null +++ b/src/citra_qt/cheat_gui.h @@ -0,0 +1,58 @@ +// 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..43c6f6402 --- /dev/null +++ b/src/citra_qt/cheat_gui.ui @@ -0,0 +1,214 @@ + + + CheatDialog + + + + 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 3946bb153..23b5de46f 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/configuration/config.h" #include "citra_qt/configuration/configure_dialog.h" #include "citra_qt/debugger/graphics/graphics.h" @@ -287,6 +288,7 @@ void GMainWindow::RestoreUIState() { microProfileDialog->setVisible(UISettings::values.microprofile_visible); #endif + ui.action_Cheats->setEnabled(false); game_list->LoadInterfaceLayout(); ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); @@ -328,6 +330,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Cheats, &QAction::triggered, this, &GMainWindow::OnCheats); // View connect(ui.action_Single_Window_Mode, &QAction::triggered, this, @@ -500,6 +503,7 @@ void GMainWindow::ShutdownGame() { disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame())); // Update the GUI + ui.action_Cheats->setEnabled(false); ui.action_Start->setEnabled(false); ui.action_Start->setText(tr("Start")); ui.action_Pause->setEnabled(false); @@ -625,6 +629,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); } @@ -713,6 +718,14 @@ void GMainWindow::OnSwapScreens() { Settings::Apply(); } +void GMainWindow::OnCheats() { + if (cheatWindow == nullptr) + { + cheatWindow = std::make_shared(this); + } + cheatWindow->show(); +} + 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 4efab9a4a..cd6d18827 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -125,6 +125,7 @@ private slots: void OnMenuRecentFile(); void OnSwapScreens(); void OnConfigure(); + void OnCheats(); void OnToggleFilterBar(); void OnDisplayTitleBars(bool); void ToggleFullscreen(); @@ -163,6 +164,7 @@ private: GraphicsVertexShaderWidget* graphicsVertexShaderWidget; GraphicsTracingWidget* graphicsTracingWidget; WaitTreeWidget* waitTreeWidget; + std::shared_ptr cheatWindow; QAction* actions_recent_files[max_recent_files_item]; diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 617343e51..0c16da85b 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -73,6 +73,7 @@ + @@ -160,6 +161,11 @@ Configure... + + + Cheats ... + + true diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 6959915fa..199f6d271 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include "common/common_paths.h" @@ -462,4 +463,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 259360aec..efb625028 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -4,8 +4,11 @@ #pragma once +#include +#include #include #include +#include #include #include #include @@ -134,4 +137,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 2618da18c..11642f562 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,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 @@ -205,6 +206,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..eaa45a8e0 --- /dev/null +++ b/src/core/cheat_core.cpp @@ -0,0 +1,465 @@ +// 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" +#include "core/hle/service/hid/hid.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.empty()) { + 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 { + 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 == CheatType::Null) + continue; + addr = line.address; + val = line.value; + if (if_flag > 0) { + if (line.type == CheatType::Patch) + i += (line.value + 7) / 8; + if (line.type == CheatType::Terminator) + if_flag--; // ENDIF + if (line.type == CheatType::FullTerminator) // 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 CheatType::Write32: { // 0XXXXXXX YYYYYYYY word[XXXXXXX+offset] = YYYYYYYY + addr = line.address + offset; + Memory::Write32(addr, val); + break; + } + case CheatType::Write16: { // 1XXXXXXX 0000YYYY half[XXXXXXX+offset] = YYYY + addr = line.address + offset; + Memory::Write16(addr, static_cast(val)); + break; + } + case CheatType::Write8: { // 2XXXXXXX 000000YY byte[XXXXXXX+offset] = YY + addr = line.address + offset; + Memory::Write8(addr, static_cast(val)); + break; + } + case CheatType::GreaterThan32: { // 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 CheatType::LessThan32: { // 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 CheatType::EqualTo32: { // 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 CheatType::NotEqualTo32: { // 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 CheatType::GreaterThan16: { // 7XXXXXXX ZZZZYYYY IF YYYY > ((not ZZZZ) AND + // half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + int x = line.address & 0x0FFFFFFF; + int y = line.value & 0xFFFF; + int z = line.value >> 16; + val = Memory::Read16(x); + if (y > (u16)((~z) & val)) + { + if (if_flag > 0) + if_flag--; + } + else { + if_flag++; + } + break; + } + case CheatType::LessThan16: { // 8XXXXXXX ZZZZYYYY IF YYYY < ((not ZZZZ) AND + // half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + int x = line.address & 0x0FFFFFFF; + int y = line.value & 0xFFFF; + int z = line.value >> 16; + val = Memory::Read16(x); + if (y < (u16)((~z) & val)) + { + if (if_flag > 0) + if_flag--; + } + else { + if_flag++; + } + break; + } + case CheatType::EqualTo16: { // 9XXXXXXX ZZZZYYYY IF YYYY = ((not ZZZZ) AND half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + int x = line.address & 0x0FFFFFFF; + int y = line.value & 0xFFFF; + int z = line.value >> 16; + val = Memory::Read16(x); + if (y == (u16)((~z) & val)) + { + if (if_flag > 0) + if_flag--; + } + else { + if_flag++; + } + break; + } + case CheatType::NotEqualTo16: { // AXXXXXXX ZZZZYYYY IF YYYY <> ((not ZZZZ) AND + // half[XXXXXXX]) + if (line.address == 0) + line.address = offset; + int x = line.address & 0x0FFFFFFF; + int y = line.value & 0xFFFF; + int z = line.value >> 16; + val = Memory::Read16(x); + if (y != (u16)((~z) & val)) + { + if (if_flag > 0) + if_flag--; + } + else { + if_flag++; + } + break; + } + case CheatType::LoadOffset: { // BXXXXXXX 00000000 offset = word[XXXXXXX+offset] + addr = line.address + offset; + offset = Memory::Read32(addr); + break; + } + case CheatType::Loop: { + if (loop_count < (line.value + 1)) + loop_flag = 1; + else + loop_flag = 0; + loop_count++; + loopbackline = i; + break; + } + case CheatType::Terminator: { + break; + } + case CheatType::LoopExecuteVariant: { + if (loop_flag) + i = loopbackline - 1; + break; + } + case CheatType::FullTerminator: { + if (loop_flag) + i = loopbackline - 1; + else { + offset = 0; + reg = 0; + loop_count = 0; + counter = 0; + if_flag = 0; + loop_flag = 0; + } + break; + } + case CheatType::SetOffset: { + offset = line.value; + break; + } + case CheatType::AddValue: { + reg += line.value; + break; + } + case CheatType::SetValue: { + reg = line.value; + break; + } + case CheatType::IncrementiveWrite32: { + addr = line.value + offset; + Memory::Write32(addr, reg); + offset += 4; + break; + } + case CheatType::IncrementiveWrite16: { + addr = line.value + offset; + Memory::Write16(addr, static_cast(reg)); + offset += 2; + break; + } + case CheatType::IncrementiveWrite8: { + addr = line.value + offset; + Memory::Write8(addr, static_cast(reg)); + offset += 1; + break; + } + case CheatType::Load32: { + addr = line.value + offset; + reg = Memory::Read32(addr); + break; + } + case CheatType::Load16: { + addr = line.value + offset; + reg = Memory::Read16(addr); + break; + } + case CheatType::Load8: { + addr = line.value + offset; + reg = Memory::Read8(addr); + break; + } + case CheatType::AddOffset: { + offset += line.value; + break; + } + case CheatType::Joker: { + auto state = Service::HID::GetInputsThisFrame(); + auto result = (state.hex & line.value) == line.value; + if (result) { + if (if_flag > 0) + if_flag--; + } + else { + if_flag++; + } + break; + } + case CheatType::Patch: { + // Patch Code (Miscellaneous Memory Manipulation Codes) + // EXXXXXXX YYYYYYYY + // Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset]. + auto x = line.address & 0x0FFFFFFF; + auto y = line.value; + addr = x + offset; + { + u32 j = 0, t = 0, b = 0; + if (y > 0) + i++; // skip over the current code + while (y >= 4) { + u32 tmp = (t == 0) ? cheat_lines[i].address : cheat_lines[i].value; + if (t == 1) + i++; + t ^= 1; + Memory::Write32(addr, tmp); + addr += 4; + y -= 4; + } + while (y > 0) { + u32 tmp = ((t == 0) ? cheat_lines[i].address : cheat_lines[i].value) >> b; + Memory::Write8(addr, tmp); + addr += 1; + y -= 1; + b += 4; + } + } + 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..f32639a34 --- /dev/null +++ b/src/core/cheat_core.h @@ -0,0 +1,168 @@ +// 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 { + +enum class CheatType { + Null = -0x1, + Write32 = 0x00, + Write16 = 0x01, + Write8 = 0x02, + GreaterThan32 = 0x03, + LessThan32 = 0x04, + EqualTo32 = 0x05, + NotEqualTo32 = 0x06, + GreaterThan16 = 0x07, + LessThan16 = 0x08, + EqualTo16 = 0x09, + NotEqualTo16 = 0x0A, + LoadOffset = 0x0B, + Loop = 0x0C, + Terminator = 0xD0, + LoopExecuteVariant = 0xD1, + FullTerminator = 0xD2, + SetOffset = 0xD3, + AddValue = 0xD4, + SetValue = 0xD5, + IncrementiveWrite32 = 0xD6, + IncrementiveWrite16 = 0xD7, + IncrementiveWrite8 = 0xD8, + Load32 = 0xD9, + Load16 = 0xDA, + Load8 = 0xDB, + AddOffset = 0xDC, + Joker = 0xDD, + Patch = 0x0E, +}; +/* + * 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 = CheatType::Null; + cheat_line = line; + return; + } + try { + std::string type_temp = line.substr(0, 1); + + // 0xD types have extra subtype value, i.e. 0xDA + std::string sub_type_temp; + if (type_temp == "D" || type_temp == "d") + sub_type_temp = line.substr(1, 1); + type = static_cast(std::stoi(type_temp + sub_type_temp, 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 = CheatType::Null; + cheat_line = line; + return; + } + } + CheatType 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 0c7a72987..a2f7b3868 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" @@ -154,6 +155,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)) { @@ -182,6 +184,7 @@ void System::Shutdown() { // Shutdown emulation session GDBStub::Shutdown(); AudioCore::Shutdown(); + CheatCore::Shutdown(); VideoCore::Shutdown(); Service::Shutdown(); Kernel::Shutdown(); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 379fbd71c..dbac35939 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -59,6 +59,7 @@ static std::array, Settings::NativeButton:: static std::unique_ptr circle_pad; static std::unique_ptr motion_device; static std::unique_ptr touch_device; +static PadState inputs_this_frame; DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions @@ -140,6 +141,7 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { state.circle_down.Assign(direction.down); state.circle_left.Assign(direction.left); state.circle_right.Assign(direction.right); + inputs_this_frame.hex = state.hex; mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; @@ -273,6 +275,10 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event); } +PadState& GetInputsThisFrame() { + return inputs_this_frame; +} + void GetIPCHandles(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index ef25926b5..3ee42b901 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -276,5 +276,7 @@ void Shutdown(); /// Reload input devices. Used when input configuration changed void ReloadInputDevices(); + +PadState& GetInputsThisFrame(); } }