CheatsModule-2

This commit is contained in:
citra 2017-10-16 21:45:00 +01:00
parent a34f43e3ea
commit f4683d7f90
15 changed files with 1200 additions and 0 deletions

View File

@ -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

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

@ -0,0 +1,211 @@
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QTableWidgetItem>
#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<Ui::CheatDialog>()) {
// 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<int>(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<std::string> 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<std::string> detail_lines;
Common::SplitString(details.toStdString(), '\n', detail_lines);
auto new_lines = std::vector<CheatEngine::CheatLine>();
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<QCheckBox*>(sender());
int row = static_cast<int>(checkbox->property("row").toInt());
cheats[row]->SetEnabled(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 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<int>(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<CheatEngine::GatewayCheat>(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() {}

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

@ -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 <memory>
#include <QDialog>
#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::CheatDialog> ui;
int current_row = -1;
bool selection_changing = false;
std::vector<std::shared_ptr<CheatEngine::CheatBase>> 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<CheatEngine::CheatBase> GetReturnValue() const {
return return_value;
}
private:
QLineEdit* name_block;
QComboBox* type_select;
std::shared_ptr<CheatEngine::CheatBase> return_value;
};

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

@ -0,0 +1,214 @@
<?xml version="1.0" encoding="utf-8"?>
<ui version="4.0">
<class>CheatDialog</class>
<widget class="QDialog" name="CheatDialog">
<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="labelAvailableCheats">
<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="labelNotes">
<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

@ -14,6 +14,7 @@
#include <QtGui>
#include <QtWidgets>
#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<CheatDialog>(this);
}
cheatWindow->show();
}
void GMainWindow::OnCreateGraphicsSurfaceViewer() {
auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);

View File

@ -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<class CheatDialog> cheatWindow;
QAction* actions_recent_files[max_recent_files_item];

View File

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

View File

@ -6,6 +6,7 @@
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <locale>
#include <cstring>
#include <boost/range/algorithm/transform.hpp>
#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<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(),
std::ostream_iterator<std::string>(os, separator));
// Drop the trailing delimiter.
std::string result = os.str();
result.pop_back();
return result;
}
}
}

View File

@ -4,8 +4,11 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <cstdarg>
#include <cstddef>
#include <functional>
#include <iomanip>
#include <sstream>
#include <string>
@ -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<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/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

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

@ -0,0 +1,465 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#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<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->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<std::shared_ptr<CheatBase>> CheatEngine::ReadFileContents() {
std::string file_path = GetFilePath();
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 =
"Gateway"; // If more cheat types added, need to specify which type in parsing.
std::vector<std::string> notes;
std::vector<CheatLine> cheat_lines;
std::vector<std::shared_ptr<CheatBase>> 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<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.front() == '[') { // Disabled code
if (!cheat_lines.empty()) {
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.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<GatewayCheat>(cheat_lines, notes, enabled, name));
}
}
}
return cheats;
}
void CheatEngine::Save(std::vector<std::shared_ptr<CheatBase>> 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<u16>(val));
break;
}
case CheatType::Write8: { // 2XXXXXXX 000000YY byte[XXXXXXX+offset] = YY
addr = line.address + offset;
Memory::Write8(addr, static_cast<u8>(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<u16>(reg));
offset += 2;
break;
}
case CheatType::IncrementiveWrite8: {
addr = line.value + offset;
Memory::Write8(addr, static_cast<u8>(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;
}
}

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

@ -0,0 +1,168 @@
// 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"
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<CheatType>(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<std::string>& GetNotes() const {
return notes;
}
void SetNotes(std::vector<std::string> 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<CheatLine>& GetCheatLines() const {
return cheat_lines;
}
void SetCheatLines(std::vector<CheatLine> 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<std::string> notes;
bool enabled = false;
std::string type;
std::vector<CheatLine> 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<CheatLine> cheat_lines_, std::vector<std::string> 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<std::shared_ptr<CheatBase>> ReadFileContents();
static void Save(std::vector<std::shared_ptr<CheatBase>> cheats);
void RefreshCheats();
private:
std::vector<std::shared_ptr<CheatBase>> cheats_list;
};
}

View File

@ -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();

View File

@ -59,6 +59,7 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::
static std::unique_ptr<Input::AnalogDevice> circle_pad;
static std::unique_ptr<Input::MotionDevice> motion_device;
static std::unique_ptr<Input::TouchDevice> 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();

View File

@ -276,5 +276,7 @@ void Shutdown();
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
PadState& GetInputsThisFrame();
}
}