Add Gateway cheat support

This commit is contained in:
Ivory Nguyen 2016-09-10 14:23:46 -05:00 committed by Anon
parent 722af0703e
commit 9d9269d893
15 changed files with 1061 additions and 4 deletions

View File

@ -18,6 +18,7 @@ set(SRCS
util/spinbox.cpp util/spinbox.cpp
util/util.cpp util/util.cpp
bootmanager.cpp bootmanager.cpp
cheat_gui.cpp
configure_audio.cpp configure_audio.cpp
configure_debug.cpp configure_debug.cpp
configure_dialog.cpp configure_dialog.cpp
@ -51,6 +52,7 @@ set(HEADERS
util/spinbox.h util/spinbox.h
util/util.h util/util.h
bootmanager.h bootmanager.h
cheat_gui.h
configure_audio.h configure_audio.h
configure_debug.h configure_debug.h
configure_dialog.h configure_dialog.h
@ -71,6 +73,7 @@ set(UIS
debugger/disassembler.ui debugger/disassembler.ui
debugger/profiler.ui debugger/profiler.ui
debugger/registers.ui debugger/registers.ui
cheat_gui.ui
configure.ui configure.ui
configure_audio.ui configure_audio.ui
configure_debug.ui configure_debug.ui

207
src/citra_qt/cheat_gui.cpp Normal file
View File

@ -0,0 +1,207 @@
#include <qcheckbox.h>
#include <QComboBox>
#include <QLineEdit>
#include <QTableWidgetItem>
#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<std::string> 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<std::string> 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<QCheckBox*>(sender());
int row = static_cast<int>(checkbox->property("row").toInt());
cheats[row]->enabled = state;
}
void CheatDialog::OnDelete() {
QItemSelectionModel* selectionModel = ui->tableCheats->selectionModel();
QModelIndexList selected = selectionModel->selectedRows();
std::vector<int> 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, [=]() {
if (typeSelect->currentIndex() == 0 && Common::Trim(nameblock->text().toStdString()).length() > 0) {
return_value = std::make_shared<CheatEngine::GatewayCheat>(std::vector<CheatEngine::CheatLine>(), std::vector<std::string>(), 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() {
}

57
src/citra_qt/cheat_gui.h Normal file
View File

@ -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 <memory>
#include <QDialog>
#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<std::shared_ptr<CheatEngine::ICheat>> 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<CheatEngine::ICheat> return_value;
private:
QDialogEx* ui;
QLineEdit* nameblock;
QComboBox* typeSelect;
};

217
src/citra_qt/cheat_gui.ui Normal file
View File

@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheatDialog</class>
<widget class="QDialog" name="CheatDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>862</width>
<height>612</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Cheats</string>
</property>
<widget class="QLabel" name="labelTitle">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>300</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>Game Title - GameID</string>
</property>
</widget>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>570</y>
<width>841</width>
<height>41</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonNewCheat">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Add Cheat</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDelete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonSave">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonClose">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>80</y>
<width>551</width>
<height>471</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="tableCheats">
<property name="showGrid">
<bool>false</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string />
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>121</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Available Cheats:</string>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<property name="geometry">
<rect>
<x>580</x>
<y>440</y>
<width>271</width>
<height>111</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="textNotes" />
</item>
</layout>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>580</x>
<y>420</y>
<width>111</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Notes:</string>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<property name="geometry">
<rect>
<x>580</x>
<y>80</y>
<width>271</width>
<height>311</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPlainTextEdit" name="textDetails" />
</item>
</layout>
</widget>
<widget class="QLabel" name="labelDetails">
<property name="geometry">
<rect>
<x>580</x>
<y>60</y>
<width>55</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Details:</string>
</property>
</widget>
</widget>
<resources />
<connections />
</ui>

View File

@ -16,6 +16,7 @@
#include "qhexedit.h" #include "qhexedit.h"
#include "citra_qt/bootmanager.h" #include "citra_qt/bootmanager.h"
#include "citra_qt/cheat_gui.h"
#include "citra_qt/config.h" #include "citra_qt/config.h"
#include "citra_qt/configure_dialog.h" #include "citra_qt/configure_dialog.h"
#include "citra_qt/game_list.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->restoreGeometry(UISettings::values.microprofile_geometry);
microProfileDialog->setVisible(UISettings::values.microprofile_visible); microProfileDialog->setVisible(UISettings::values.microprofile_visible);
#endif #endif
ui.action_Cheats->setEnabled(false);
game_list->LoadInterfaceLayout(); game_list->LoadInterfaceLayout();
@ -169,6 +171,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
// Setup connections // Setup connections
connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection); connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection);
connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); 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_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),Qt::DirectConnection);
connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap()));
connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot())); 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_Start->setText(tr("Start"));
ui.action_Pause->setEnabled(false); ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false); ui.action_Stop->setEnabled(false);
ui.action_Cheats->setEnabled(false);
render_window->hide(); render_window->hide();
game_list->show(); game_list->show();
@ -467,6 +471,7 @@ void GMainWindow::OnStartGame() {
ui.action_Start->setEnabled(false); ui.action_Start->setEnabled(false);
ui.action_Start->setText(tr("Continue")); ui.action_Start->setText(tr("Continue"));
ui.action_Cheats->setEnabled(true);
ui.action_Pause->setEnabled(true); ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true); ui.action_Stop->setEnabled(true);
@ -520,6 +525,11 @@ void GMainWindow::OnConfigure() {
} }
} }
void GMainWindow::OnCheats() {
CheatDialog cheatDialog(this);
cheatDialog.exec();
}
void GMainWindow::OnCreateGraphicsSurfaceViewer() { void GMainWindow::OnCreateGraphicsSurfaceViewer() {
auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this); auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget); addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);

View File

@ -106,6 +106,7 @@ private slots:
void OnMenuSelectGameListRoot(); void OnMenuSelectGameListRoot();
void OnMenuRecentFile(); void OnMenuRecentFile();
void OnConfigure(); void OnConfigure();
void OnCheats();
void OnDisplayTitleBars(bool); void OnDisplayTitleBars(bool);
void ToggleWindowMode(); void ToggleWindowMode();
void OnCreateGraphicsSurfaceViewer(); void OnCreateGraphicsSurfaceViewer();

View File

@ -74,6 +74,7 @@
<addaction name="action_Stop"/> <addaction name="action_Stop"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Configure"/> <addaction name="action_Configure"/>
<addaction name="action_Cheats"/>
</widget> </widget>
<widget class="QMenu" name="menu_View"> <widget class="QMenu" name="menu_View">
<property name="title"> <property name="title">
@ -151,6 +152,11 @@
<string>Configure ...</string> <string>Configure ...</string>
</property> </property>
</action> </action>
<action name="action_Cheats">
<property name="text">
<string>Cheats ...</string>
</property>
</action>
<action name="actionDisplay_widget_title_bars"> <action name="actionDisplay_widget_title_bars">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>

View File

@ -504,4 +504,34 @@ std::string StringFromFixedZeroTerminatedBuffer(const char * buffer, size_t max_
return std::string(buffer, len); 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<int, int>(std::isspace))));
return s;
}
std::string RTrim(std::string & s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}
std::string Trim(std::string & s) {
return LTrim(RTrim(s));
}
std::string Join(const std::vector<std::string>& 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<std::string>(os, separator));
os << *elements.rbegin();
return os.str();
}
}
} }

View File

@ -4,9 +4,13 @@
#pragma once #pragma once
#include <algorithm>
#include <cctype>
#include <cstdarg> #include <cstdarg>
#include <cstddef> #include <cstddef>
#include <functional>
#include <iomanip> #include <iomanip>
#include <locale>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
@ -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 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<std::string>& elements, const char* const separator);
} }

View File

@ -12,6 +12,7 @@ set(SRCS
arm/skyeye_common/vfp/vfpdouble.cpp arm/skyeye_common/vfp/vfpdouble.cpp
arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpinstr.cpp
arm/skyeye_common/vfp/vfpsingle.cpp arm/skyeye_common/vfp/vfpsingle.cpp
cheat_core.cpp
core.cpp core.cpp
core_timing.cpp core_timing.cpp
file_sys/archive_backend.cpp file_sys/archive_backend.cpp
@ -153,6 +154,7 @@ set(HEADERS
arm/skyeye_common/vfp/asm_vfp.h arm/skyeye_common/vfp/asm_vfp.h
arm/skyeye_common/vfp/vfp.h arm/skyeye_common/vfp/vfp.h
arm/skyeye_common/vfp/vfp_helper.h arm/skyeye_common/vfp/vfp_helper.h
cheat_core.h
core.h core.h
core_timing.h core_timing.h
file_sys/archive_backend.h file_sys/archive_backend.h

413
src/core/cheat_core.cpp Normal file
View File

@ -0,0 +1,413 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#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<CheatEngine::CheatEngine> cheat_engine;
static void CheatTickCallback(u64, int cycles_late) {
if (cheat_engine == nullptr)
cheat_engine = std::make_unique<CheatEngine::CheatEngine>();
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<CheatEngine::CheatEngine>();
}
}
namespace CheatEngine {
CheatEngine::CheatEngine() {
//Create folder and file for cheats if it doesn't exist
FileUtil::CreateDir(FileUtil::GetExeDirectory() + "\\user\\cheats");
char buffer[50];
sprintf(buffer, "%016llX", Loader::program_id);
std::string file_path = FileUtil::GetExeDirectory() + "\\user\\cheats\\" + std::string(buffer) + ".txt";
if (!FileUtil::Exists(file_path))
FileUtil::CreateEmptyFile(file_path);
cheats_list = ReadFileContents();
}
std::vector<std::shared_ptr<ICheat>> CheatEngine::ReadFileContents() {
char buffer[50];
auto a = sprintf(buffer, "%016llX", Loader::program_id);
std::string file_path = FileUtil::GetExeDirectory() + "\\user\\cheats\\" + std::string(buffer) + ".txt";
std::string contents;
FileUtil::ReadFileToString(true, file_path.c_str(), contents);
std::vector<std::string> lines;
Common::SplitString(contents, '\n', lines);
std::string code_type;
std::vector<std::string> notes;
std::vector<CheatLine> cheat_lines;
std::vector<std::shared_ptr<ICheat>> cheats;
std::string name;
bool enabled = false;
for (int i = 0; i < lines.size(); i++) {
std::string current_line = Common::Trim(std::string(lines[i].c_str()));
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<GatewayCheat>(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<GatewayCheat>(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<GatewayCheat>(cheat_lines, notes, enabled, name));
}
}
}
return cheats;
}
void CheatEngine::Save(std::vector<std::shared_ptr<ICheat>> cheats) {
char buffer[50];
auto a = sprintf(buffer, "%016llX", Loader::program_id);
std::string file_path = FileUtil::GetExeDirectory() + "\\user\\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<u16>(val));
break;
}
case 0x02: { // 2XXXXXXX 000000YY byte[XXXXXXX+offset] = YY
addr = line.address + offset;
Memory::Write8(addr, static_cast<u8>(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<u16>(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<u16>(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<u16>(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<u16>(reg));
offset += 2;
break;
}
case 0x08: {
addr = line.value + offset;
Memory::Write8(addr, static_cast<u8>(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;
}
}

96
src/core/cheat_core.h Normal file
View File

@ -0,0 +1,96 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <map>
#include <memory>
#include <vector>
#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 = Common::Trim(std::string(line.c_str())); // remove '/0' characters if any.
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<std::string> notes;
bool enabled;
std::string type;
std::vector<CheatLine> cheat_lines;
std::string name;
};
/*
* Implements support for Gateway (GateShark) cheats.
*/
class GatewayCheat : public ICheat {
public:
GatewayCheat(std::vector<CheatLine> _cheatlines, std::vector<std::string> _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<std::shared_ptr<ICheat>> ReadFileContents();
static void Save(std::vector<std::shared_ptr<ICheat>> cheats);
private:
std::vector<std::shared_ptr<ICheat>> cheats_list;
};
}

View File

@ -24,7 +24,7 @@ namespace Loader {
static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs
static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) 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 * Get the decompressed size of an LZSS compressed ExeFS file
* @param buffer Buffer of compressed 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, "Core version: %d" , core_version);
LOG_DEBUG(Loader, "Thread priority: 0x%X" , priority); LOG_DEBUG(Loader, "Thread priority: 0x%X" , priority);
LOG_DEBUG(Loader, "Resource limit category: %d" , resource_limit_category); 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) { 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."); LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted.");
return ResultStatus::ErrorEncrypted; return ResultStatus::ErrorEncrypted;

View File

@ -159,7 +159,7 @@ static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wron
// Loader namespace // Loader namespace
namespace Loader { namespace Loader {
extern u64_le program_id;
/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) /// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI)
class AppLoader_NCCH final : public AppLoader { class AppLoader_NCCH final : public AppLoader {
public: public:

View File

@ -4,6 +4,7 @@
#include "audio_core/audio_core.h" #include "audio_core/audio_core.h"
#include "core/cheat_core.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/system.h" #include "core/system.h"
@ -12,7 +13,6 @@
#include "core/hle/hle.h" #include "core/hle/hle.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory.h" #include "core/hle/kernel/memory.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
namespace System { namespace System {
@ -30,6 +30,8 @@ Result Init(EmuWindow* emu_window) {
return Result::ErrorInitVideoCore; return Result::ErrorInitVideoCore;
} }
AudioCore::Init(); AudioCore::Init();
InputCore::Init();
CheatCore::Init();
GDBStub::Init(); GDBStub::Init();
is_powered_on = true; is_powered_on = true;
@ -43,6 +45,8 @@ bool IsPoweredOn() {
void Shutdown() { void Shutdown() {
GDBStub::Shutdown(); GDBStub::Shutdown();
CheatCore::Shutdown();
InputCore::Shutdown();
AudioCore::Shutdown(); AudioCore::Shutdown();
VideoCore::Shutdown(); VideoCore::Shutdown();
HLE::Shutdown(); HLE::Shutdown();