mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-27 00:30:06 +00:00
CheatsModule-2
This commit is contained in:
parent
a34f43e3ea
commit
7df94fff92
@ -26,6 +26,7 @@ set(SRCS
|
|||||||
util/spinbox.cpp
|
util/spinbox.cpp
|
||||||
util/util.cpp
|
util/util.cpp
|
||||||
bootmanager.cpp
|
bootmanager.cpp
|
||||||
|
cheat_gui.cpp
|
||||||
game_list.cpp
|
game_list.cpp
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
@ -58,6 +59,7 @@ set(HEADERS
|
|||||||
util/spinbox.h
|
util/spinbox.h
|
||||||
util/util.h
|
util/util.h
|
||||||
bootmanager.h
|
bootmanager.h
|
||||||
|
cheat_gui.h
|
||||||
game_list.h
|
game_list.h
|
||||||
game_list_p.h
|
game_list_p.h
|
||||||
hotkeys.h
|
hotkeys.h
|
||||||
@ -74,6 +76,7 @@ set(UIS
|
|||||||
configuration/configure_input.ui
|
configuration/configure_input.ui
|
||||||
configuration/configure_system.ui
|
configuration/configure_system.ui
|
||||||
configuration/configure_web.ui
|
configuration/configure_web.ui
|
||||||
|
cheat_gui.ui
|
||||||
debugger/registers.ui
|
debugger/registers.ui
|
||||||
hotkeys.ui
|
hotkeys.ui
|
||||||
main.ui
|
main.ui
|
||||||
|
211
src/citra_qt/cheat_gui.cpp
Normal file
211
src/citra_qt/cheat_gui.cpp
Normal 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
58
src/citra_qt/cheat_gui.h
Normal 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
214
src/citra_qt/cheat_gui.ui
Normal 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>
|
@ -14,6 +14,7 @@
|
|||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
#include "citra_qt/bootmanager.h"
|
#include "citra_qt/bootmanager.h"
|
||||||
|
#include "citra_qt/cheat_gui.h"
|
||||||
#include "citra_qt/configuration/config.h"
|
#include "citra_qt/configuration/config.h"
|
||||||
#include "citra_qt/configuration/configure_dialog.h"
|
#include "citra_qt/configuration/configure_dialog.h"
|
||||||
#include "citra_qt/debugger/graphics/graphics.h"
|
#include "citra_qt/debugger/graphics/graphics.h"
|
||||||
@ -287,6 +288,7 @@ void GMainWindow::RestoreUIState() {
|
|||||||
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();
|
||||||
|
|
||||||
ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode);
|
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_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
|
||||||
connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
|
connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
|
||||||
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
|
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
|
||||||
|
connect(ui.action_Cheats, &QAction::triggered, this, &GMainWindow::OnCheats);
|
||||||
|
|
||||||
// View
|
// View
|
||||||
connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
|
connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
|
||||||
@ -500,6 +503,7 @@ void GMainWindow::ShutdownGame() {
|
|||||||
disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
|
disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
|
||||||
|
|
||||||
// Update the GUI
|
// Update the GUI
|
||||||
|
ui.action_Cheats->setEnabled(false);
|
||||||
ui.action_Start->setEnabled(false);
|
ui.action_Start->setEnabled(false);
|
||||||
ui.action_Start->setText(tr("Start"));
|
ui.action_Start->setText(tr("Start"));
|
||||||
ui.action_Pause->setEnabled(false);
|
ui.action_Pause->setEnabled(false);
|
||||||
@ -625,6 +629,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);
|
||||||
}
|
}
|
||||||
@ -713,6 +718,14 @@ void GMainWindow::OnSwapScreens() {
|
|||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnCheats() {
|
||||||
|
if (cheatWindow == nullptr)
|
||||||
|
{
|
||||||
|
cheatWindow = std::make_shared<CheatDialog>(this);
|
||||||
|
}
|
||||||
|
cheatWindow->show();
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -125,6 +125,7 @@ private slots:
|
|||||||
void OnMenuRecentFile();
|
void OnMenuRecentFile();
|
||||||
void OnSwapScreens();
|
void OnSwapScreens();
|
||||||
void OnConfigure();
|
void OnConfigure();
|
||||||
|
void OnCheats();
|
||||||
void OnToggleFilterBar();
|
void OnToggleFilterBar();
|
||||||
void OnDisplayTitleBars(bool);
|
void OnDisplayTitleBars(bool);
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
@ -163,6 +164,7 @@ private:
|
|||||||
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
||||||
GraphicsTracingWidget* graphicsTracingWidget;
|
GraphicsTracingWidget* graphicsTracingWidget;
|
||||||
WaitTreeWidget* waitTreeWidget;
|
WaitTreeWidget* waitTreeWidget;
|
||||||
|
std::shared_ptr<class CheatDialog> cheatWindow;
|
||||||
|
|
||||||
QAction* actions_recent_files[max_recent_files_item];
|
QAction* actions_recent_files[max_recent_files_item];
|
||||||
|
|
||||||
|
@ -73,6 +73,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">
|
||||||
@ -160,6 +161,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="action_Display_Dock_Widget_Headers">
|
<action name="action_Display_Dock_Widget_Headers">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <locale>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <boost/range/algorithm/transform.hpp>
|
#include <boost/range/algorithm/transform.hpp>
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
@ -462,4 +463,43 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l
|
|||||||
|
|
||||||
return std::string(buffer, len);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#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.
|
* NUL-terminated then the string ends at max_len characters.
|
||||||
*/
|
*/
|
||||||
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -205,6 +206,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
|
||||||
|
465
src/core/cheat_core.cpp
Normal file
465
src/core/cheat_core.cpp
Normal 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
168
src/core/cheat_core.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||||
#include "core/arm/dyncom/arm_dyncom.h"
|
#include "core/arm/dyncom/arm_dyncom.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/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
@ -154,6 +155,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
|||||||
Kernel::Init(system_mode);
|
Kernel::Init(system_mode);
|
||||||
Service::Init();
|
Service::Init();
|
||||||
AudioCore::Init();
|
AudioCore::Init();
|
||||||
|
CheatCore::Init();
|
||||||
GDBStub::Init();
|
GDBStub::Init();
|
||||||
|
|
||||||
if (!VideoCore::Init(emu_window)) {
|
if (!VideoCore::Init(emu_window)) {
|
||||||
@ -182,6 +184,7 @@ void System::Shutdown() {
|
|||||||
// Shutdown emulation session
|
// Shutdown emulation session
|
||||||
GDBStub::Shutdown();
|
GDBStub::Shutdown();
|
||||||
AudioCore::Shutdown();
|
AudioCore::Shutdown();
|
||||||
|
CheatCore::Shutdown();
|
||||||
VideoCore::Shutdown();
|
VideoCore::Shutdown();
|
||||||
Service::Shutdown();
|
Service::Shutdown();
|
||||||
Kernel::Shutdown();
|
Kernel::Shutdown();
|
||||||
|
@ -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::AnalogDevice> circle_pad;
|
||||||
static std::unique_ptr<Input::MotionDevice> motion_device;
|
static std::unique_ptr<Input::MotionDevice> motion_device;
|
||||||
static std::unique_ptr<Input::TouchDevice> touch_device;
|
static std::unique_ptr<Input::TouchDevice> touch_device;
|
||||||
|
static PadState inputs_this_frame;
|
||||||
|
|
||||||
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
||||||
// 30 degree and 60 degree are angular thresholds for directions
|
// 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_down.Assign(direction.down);
|
||||||
state.circle_left.Assign(direction.left);
|
state.circle_left.Assign(direction.left);
|
||||||
state.circle_right.Assign(direction.right);
|
state.circle_right.Assign(direction.right);
|
||||||
|
inputs_this_frame.hex = state.hex;
|
||||||
|
|
||||||
mem->pad.current_state.hex = state.hex;
|
mem->pad.current_state.hex = state.hex;
|
||||||
mem->pad.index = next_pad_index;
|
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);
|
CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PadState& GetInputsThisFrame() {
|
||||||
|
return inputs_this_frame;
|
||||||
|
}
|
||||||
|
|
||||||
void GetIPCHandles(Service::Interface* self) {
|
void GetIPCHandles(Service::Interface* self) {
|
||||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||||
|
|
||||||
|
@ -276,5 +276,7 @@ void Shutdown();
|
|||||||
|
|
||||||
/// Reload input devices. Used when input configuration changed
|
/// Reload input devices. Used when input configuration changed
|
||||||
void ReloadInputDevices();
|
void ReloadInputDevices();
|
||||||
|
|
||||||
|
PadState& GetInputsThisFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user