diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index efe6c5fb8..98eec35dd 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -18,6 +18,7 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp + cheat_gui.cpp configure_audio.cpp configure_debug.cpp configure_dialog.cpp @@ -53,6 +54,7 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h + cheat_gui.h configure_audio.h configure_debug.h configure_dialog.h @@ -75,6 +77,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..4cdc29ae5 --- /dev/null +++ b/src/citra_qt/cheat_gui.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include + +#include "cheat_gui.h" +#include "core/loader/ncch.h" +#include "ui_cheat_gui.h" + +CheatDialog::CheatDialog(QWidget *parent) : + QDialog(parent), ui(new Ui::CheatDialog) { + + //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); + char buffer[50]; + auto a = sprintf(buffer, "%016llX", Loader::program_id); + auto game_id = std::string(buffer); + ui->labelTitle->setText("Title ID: " + QString::fromStdString(game_id)); + + connect(ui->buttonClose, SIGNAL(released()), this, SLOT(OnCancel())); + connect(ui->buttonNewCheat, SIGNAL(released()), this, SLOT(OnAddCheat())); + connect(ui->buttonSave, SIGNAL(released()), this, SLOT(OnSave())); + connect(ui->buttonDelete, SIGNAL(released()), this, SLOT(OnDelete())); + connect(ui->tableCheats, SIGNAL(cellClicked(int, int)), this, SLOT(OnRowSelected(int, int))); + connect(ui->textDetails, SIGNAL(textChanged()), this, SLOT(OnDetailsChanged())); + connect(ui->textNotes, SIGNAL(textChanged()), this, SLOT(OnNotesChanged())); + + LoadCheats(); +} + +CheatDialog::~CheatDialog() { + delete ui; +} + +void CheatDialog::LoadCheats() { + cheats = CheatEngine::CheatEngine::ReadFileContents(); + + ui->tableCheats->setRowCount(cheats.size()); + + for (int i = 0; i < cheats.size(); i++) { + auto enabled = new QCheckBox(""); + enabled->setCheckState(cheats[i]->enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + 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]->name))); + ui->tableCheats->setItem(i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->type))); + enabled->setProperty("row", i); + connect(enabled, SIGNAL(stateChanged(int)), this, SLOT(OnCheckChanged(int))); + } +} + +void CheatDialog::OnSave() { + CheatEngine::CheatEngine::Save(cheats); + CheatCore::RefreshCheats(); + this->close(); +} + +void CheatDialog::OnCancel() { + this->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); + auto current_cheat = cheats[row]; + ui->textNotes->setPlainText(QString::fromStdString(Common::Join(current_cheat->notes, "\n"))); + + std::vector details; + for (auto& line : current_cheat->cheat_lines) + 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(); + Common::SplitString(notes.toStdString(), '\n', cheats[current_row]->notes); +} + +void CheatDialog::OnDetailsChanged() { + if (selection_changing) + return; + auto details = ui->textDetails->toPlainText(); + std::vector detail_lines; + Common::SplitString(details.toStdString(), '\n', detail_lines); + cheats[current_row]->cheat_lines.clear(); + for (auto& line : detail_lines) { + cheats[current_row]->cheat_lines.push_back(CheatEngine::CheatLine(line)); + } +} + +void CheatDialog::OnCheckChanged(int state) { + QCheckBox* checkbox = qobject_cast(sender()); + int row = static_cast(checkbox->property("row").toInt()); + cheats[row]->enabled = 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 i = 0; i < rows.size(); i++) + ui->tableCheats->removeRow(rows[i]); + + ui->tableCheats->clearSelection(); + OnRowSelected(-1, -1); +} + +void CheatDialog::OnAddCheat() { + QDialogEx* dialog = new QDialogEx(this); + + dialog->exec(); + + auto result = dialog->return_value; + if (result == nullptr) + return; + cheats.push_back(result); + int i = 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(i, 0, new QTableWidgetItem("")); + ui->tableCheats->setCellWidget(i, 0, enabled); + ui->tableCheats->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(cheats[i]->name))); + ui->tableCheats->setItem(i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->type))); + enabled->setProperty("row", i); + connect(enabled, SIGNAL(stateChanged(int)), this, SLOT(OnCheckChanged(int))); + ui->tableCheats->selectRow(i); + OnRowSelected(i, 0); + delete dialog; +} +QDialogEx::QDialogEx(QWidget *parent) : + QDialog(parent), ui(this) { + this->resize(250, 150); + this->setSizeGripEnabled(false); + auto mainLayout = new QVBoxLayout(this); + + QHBoxLayout* panel = new QHBoxLayout(); + nameblock = new QLineEdit(); + QLabel* nameLabel = new QLabel(); + nameLabel->setText("Name: "); + panel->addWidget(nameLabel); + panel->addWidget(nameblock); + + QHBoxLayout* panel2 = new QHBoxLayout(); + auto typeLabel = new QLabel(); + typeLabel->setText("Type: "); + typeSelect = new QComboBox(); + typeSelect->addItem("Gateway", 0); + panel2->addWidget(typeLabel); + panel2->addWidget(typeSelect); + + QHBoxLayout* panel3 = new QHBoxLayout(); + auto buttonOk = new QPushButton(); + buttonOk->setText("Ok"); + auto buttonCancel = new QPushButton(); + buttonCancel->setText("Cancel"); + connect(buttonOk, &QPushButton::released, this, [=]() { + auto name = nameblock->text().toStdString(); + if (typeSelect->currentIndex() == 0 && Common::Trim(name).length() > 0) { + return_value = std::make_shared(std::vector(), std::vector(), false, nameblock->text().toStdString()); + } + ui->close(); + }); + connect(buttonCancel, &QPushButton::released, this, [=]() {ui->close(); }); + + panel3->addWidget(buttonOk); + panel3->addWidget(buttonCancel); + mainLayout->addLayout(panel); + mainLayout->addLayout(panel2); + mainLayout->addLayout(panel3); +} +QDialogEx::~QDialogEx() { +} \ 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..bcfb1db8e --- /dev/null +++ b/src/citra_qt/cheat_gui.h @@ -0,0 +1,57 @@ +// 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 QDialogEx; +} + +class CheatDialog : public QDialog { + Q_OBJECT + +public: + explicit CheatDialog(QWidget* parent = 0); + ~CheatDialog(); + +private: + Ui::CheatDialog* 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 QDialogEx : public QDialog { + Q_OBJECT +public: + explicit QDialogEx(QWidget* parent = 0); + ~QDialogEx(); + std::shared_ptr return_value; +private: + QDialogEx* ui; + QLineEdit* nameblock; + QComboBox* typeSelect; +}; diff --git a/src/citra_qt/cheat_gui.ui b/src/citra_qt/cheat_gui.ui new file mode 100644 index 000000000..e432d0dde --- /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 a9b28d59d..feefce226 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -16,6 +16,7 @@ #include "qhexedit.h" #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/game_list.h" @@ -147,6 +148,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); microProfileDialog->setVisible(UISettings::values.microprofile_visible); #endif + ui.action_Cheats->setEnabled(false); game_list->LoadInterfaceLayout(); @@ -169,6 +171,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) // Setup connections connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), 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())); connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot())); @@ -376,6 +379,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(); @@ -467,6 +471,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); @@ -519,6 +524,11 @@ void GMainWindow::OnConfigure() { } } +void GMainWindow::OnCheats() { + CheatDialog cheatDialog(this); + cheatDialog.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 b836b13fb..bff76fce1 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -106,6 +106,7 @@ private slots: void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); 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 441e0b81e..6b553e282 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 f0aa072db..462992ee6 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -504,4 +504,36 @@ std::string StringFromFixedZeroTerminatedBuffer(const char * buffer, size_t max_ return std::string(buffer, len); } +std::string LTrim(std::string & s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + std::not1(std::ptr_fun(std::isspace)))); + return s; +} + +std::string RTrim(std::string & s) { + s.erase(std::find_if(s.rbegin(), s.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; +} + +std::string Trim(std::string & s) { + auto temp = RTrim(s); + auto temp2 = LTrim(temp); + return temp2; +} + +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() - 1, std::ostream_iterator(os, separator)); + os << *elements.rbegin(); + return os.str(); + } +} + } diff --git a/src/common/string_util.h b/src/common/string_util.h index 89d9f133e..f92ad8cb4 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 @@ -135,4 +139,11 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { */ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); +std::string LTrim(std::string &s); + +std::string RTrim(std::string &s); + +std::string Trim(std::string &s); + +std::string Join(const std::vector& elements, const char* const separator); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 174e9dc79..1aae4c5aa 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 @@ -153,6 +154,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..0dd6d6a4e --- /dev/null +++ b/src/core/cheat_core.cpp @@ -0,0 +1,414 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/file_util.h" +#include "core/cheat_core.h" +#include "core/loader/ncch.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.reset(); + cheat_engine = std::make_unique(); +} +} + +namespace CheatEngine { + CheatEngine::CheatEngine() { + //Create folder and file for cheats if it doesn't exist + FileUtil::CreateDir(FileUtil::GetUserPath(D_USER_IDX) + "\\cheats"); + char buffer[50]; + sprintf(buffer, "%016llX", Loader::program_id); + std::string file_path = FileUtil::GetUserPath(D_USER_IDX) + "\\cheats\\" + std::string(buffer) + ".txt"; + if (!FileUtil::Exists(file_path)) + FileUtil::CreateEmptyFile(file_path); + cheats_list = ReadFileContents(); + } + + std::vector> CheatEngine::ReadFileContents() { + char buffer[50]; + auto a = sprintf(buffer, "%016llX", Loader::program_id); + std::string file_path = FileUtil::GetUserPath(D_USER_IDX) + "\\cheats\\" + std::string(buffer) + ".txt"; + + std::string contents; + FileUtil::ReadFileToString(true, file_path.c_str(), contents); + std::vector lines; + Common::SplitString(contents, '\n', lines); + + std::string code_type; + std::vector notes; + std::vector cheat_lines; + std::vector> cheats; + std::string name; + bool enabled = false; + for (int 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 == "[Gateway]") { // Codetype header + code_type = "Gateway"; + continue; + } + if (code_type == "") + continue; + if (current_line.substr(0, 2) == "+[") { // Enabled code + if (cheat_lines.size() > 0) { + 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.substr(0, 1) == "[") { // Disabled code + if (cheat_lines.size() > 0) { + 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.substr(0, 1) == "*") { // Comment + notes.push_back(current_line); + } + else if (current_line.length() > 0) { + cheat_lines.push_back(CheatLine(current_line)); + } + if (i == lines.size() - 1) { // End of file + if (cheat_lines.size() > 0) { + if (code_type == "Gateway") + cheats.push_back(std::make_shared(cheat_lines, notes, enabled, name)); + } + } + } + return cheats; + } + + void CheatEngine::Save(std::vector> cheats) { + char buffer[50]; + auto a = sprintf(buffer, "%016llX", Loader::program_id); + std::string file_path = FileUtil::GetUserPath(D_USER_IDX) + "\\cheats\\" + std::string(buffer) + ".txt"; + FileUtil::IOFile file = FileUtil::IOFile(file_path, "w+"); + bool sectionGateway = false; + for (auto& cheat : cheats) { + if (cheat->type == "Gateway") { + if (sectionGateway == false) { + file.WriteBytes("[Gateway]\n", 10); + sectionGateway = true; + } + file.WriteBytes(cheat->ToString().c_str(), cheat->ToString().length()); + } + } + file.Close(); + } + + 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 (int 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.size() == 0) + return result; + if (enabled) + result += "+"; + result += "[" + name + "]\n"; + for (auto& str : notes) { + if (str.substr(0, 1) != "*") + str.insert(0, "*"); + } + result += Common::Join(notes, "\n"); + if (notes.size() > 0) + result += "\n"; + for (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..142c4929e --- /dev/null +++ b/src/core/cheat_core.h @@ -0,0 +1,97 @@ +// 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" +/* + * Starting point for running cheat codes. Initializes tick event and executes cheats every tick. + */ +namespace CheatCore { + void Init(); + void Shutdown(); + void RefreshCheats(); +} +namespace CheatEngine { + /* + * Represents a single line of a cheat, i.e. 1xxxxxxxx yyyyyyyy + */ + struct CheatLine { + CheatLine(std::string line) { + line = std::string(line.c_str()); // remove '/0' characters if any. + line = Common::Trim(line); + if (line.length() != 17) { + type = -1; + cheat_line = line; + return; + } + try { + type = stoi(line.substr(0, 1), 0, 16); + if (type == 0xD) + sub_type = stoi(line.substr(1, 1), 0, 16); + address = stoi(line.substr(1, 8), 0, 16); + value = 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 ICheat { + public: + virtual void Execute() = 0; + virtual ~ICheat() = default; + virtual std::string ToString() = 0; + + std::vector notes; + bool enabled; + std::string type; + std::vector cheat_lines; + std::string name; + }; + /* + * Implements support for Gateway (GateShark) cheats. + */ + class GatewayCheat : public ICheat { + public: + GatewayCheat(std::vector _cheatlines, std::vector _notes, bool _enabled, std::string _name) { + cheat_lines = _cheatlines; + notes = _notes; + enabled = _enabled; + name = _name; + type = "Gateway"; + }; + void Execute() override; + std::string ToString() override; + private: + }; + + /* + * Handles loading/saving of cheats and executing them. + */ + class CheatEngine { + public: + CheatEngine(); + void Run(); + static std::vector> ReadFileContents(); + static void Save(std::vector> cheats); + private: + std::vector> cheats_list; + }; +} diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index fca091ff9..d1d386fa7 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -24,7 +24,7 @@ namespace Loader { static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) - +u64_le program_id = 0; /** * Get the decompressed size of an LZSS compressed ExeFS file * @param buffer Buffer of compressed file @@ -273,7 +273,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { LOG_DEBUG(Loader, "Core version: %d" , core_version); LOG_DEBUG(Loader, "Thread priority: 0x%X" , priority); LOG_DEBUG(Loader, "Resource limit category: %d" , resource_limit_category); - + Loader::program_id = ncch_header.program_id; if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); return ResultStatus::ErrorEncrypted; diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 75609ee57..6b967278a 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -159,7 +159,7 @@ static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wron // Loader namespace namespace Loader { - + extern u64_le program_id; /// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) class AppLoader_NCCH final : public AppLoader { public: diff --git a/src/core/system.cpp b/src/core/system.cpp index 1d746817a..25f9ca9cf 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4,6 +4,7 @@ #include "audio_core/audio_core.h" +#include "core/cheat_core.h" #include "core/core.h" #include "core/core_timing.h" #include "core/system.h" @@ -12,7 +13,6 @@ #include "core/hle/hle.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory.h" - #include "video_core/video_core.h" #include "input_core/input_core.h" @@ -31,6 +31,7 @@ Result Init(EmuWindow* emu_window) { return Result::ErrorInitVideoCore; } AudioCore::Init(); + CheatCore::Init(); InputCore::Init(); GDBStub::Init(); @@ -45,6 +46,7 @@ bool IsPoweredOn() { void Shutdown() { GDBStub::Shutdown(); + CheatCore::Shutdown(); InputCore::Shutdown(); AudioCore::Shutdown(); VideoCore::Shutdown();