mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #4610 from zhaowenlan1779/cheats-ui
Implement UI for adding/editing/deleting cheats
This commit is contained in:
		
						commit
						3ea30fe2b3
					
				
					 8 changed files with 512 additions and 196 deletions
				
			
		|  | @ -3,10 +3,12 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <QCheckBox> | #include <QCheckBox> | ||||||
|  | #include <QMessageBox> | ||||||
| #include <QTableWidgetItem> | #include <QTableWidgetItem> | ||||||
| #include "citra_qt/cheats.h" | #include "citra_qt/cheats.h" | ||||||
| #include "core/cheats/cheat_base.h" | #include "core/cheats/cheat_base.h" | ||||||
| #include "core/cheats/cheats.h" | #include "core/cheats/cheats.h" | ||||||
|  | #include "core/cheats/gateway_cheat.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "ui_cheats.h" | #include "ui_cheats.h" | ||||||
|  | @ -21,14 +23,23 @@ CheatDialog::CheatDialog(QWidget* parent) | ||||||
|     ui->tableCheats->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); |     ui->tableCheats->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); | ||||||
|     ui->tableCheats->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); |     ui->tableCheats->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); | ||||||
|     ui->tableCheats->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); |     ui->tableCheats->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); | ||||||
|     ui->textDetails->setEnabled(false); |     ui->lineName->setEnabled(false); | ||||||
|  |     ui->textCode->setEnabled(false); | ||||||
|     ui->textNotes->setEnabled(false); |     ui->textNotes->setEnabled(false); | ||||||
|     const auto game_id = fmt::format( |     const auto game_id = fmt::format( | ||||||
|         "{:016X}", Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); |         "{:016X}", Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); | ||||||
|     ui->labelTitle->setText(tr("Title ID: %1").arg(QString::fromStdString(game_id))); |     ui->labelTitle->setText(tr("Title ID: %1").arg(QString::fromStdString(game_id))); | ||||||
| 
 | 
 | ||||||
|     connect(ui->buttonClose, &QPushButton::released, this, &CheatDialog::OnCancel); |     connect(ui->buttonClose, &QPushButton::released, this, &CheatDialog::OnCancel); | ||||||
|  |     connect(ui->buttonAddCheat, &QPushButton::released, this, &CheatDialog::OnAddCheat); | ||||||
|     connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected); |     connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected); | ||||||
|  |     connect(ui->lineName, &QLineEdit::textEdited, this, &CheatDialog::OnTextEdited); | ||||||
|  |     connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited); | ||||||
|  |     connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited); | ||||||
|  | 
 | ||||||
|  |     connect(ui->buttonSave, &QPushButton::released, | ||||||
|  |             [this] { SaveCheat(ui->tableCheats->currentRow()); }); | ||||||
|  |     connect(ui->buttonDelete, &QPushButton::released, this, &CheatDialog::OnDeleteCheat); | ||||||
| 
 | 
 | ||||||
|     LoadCheats(); |     LoadCheats(); | ||||||
| } | } | ||||||
|  | @ -36,7 +47,7 @@ CheatDialog::CheatDialog(QWidget* parent) | ||||||
| CheatDialog::~CheatDialog() = default; | CheatDialog::~CheatDialog() = default; | ||||||
| 
 | 
 | ||||||
| void CheatDialog::LoadCheats() { | void CheatDialog::LoadCheats() { | ||||||
|     const auto& cheats = Core::System::GetInstance().CheatEngine().GetCheats(); |     cheats = Core::System::GetInstance().CheatEngine().GetCheats(); | ||||||
| 
 | 
 | ||||||
|     ui->tableCheats->setRowCount(cheats.size()); |     ui->tableCheats->setRowCount(cheats.size()); | ||||||
| 
 | 
 | ||||||
|  | @ -56,20 +67,184 @@ void CheatDialog::LoadCheats() { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool CheatDialog::CheckSaveCheat() { | ||||||
|  |     auto answer = QMessageBox::warning( | ||||||
|  |         this, tr("Cheats"), tr("Would you like to save the current cheat?"), | ||||||
|  |         QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel); | ||||||
|  | 
 | ||||||
|  |     if (answer == QMessageBox::Yes) { | ||||||
|  |         return SaveCheat(last_row); | ||||||
|  |     } else { | ||||||
|  |         return answer != QMessageBox::Cancel; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool CheatDialog::SaveCheat(int row) { | ||||||
|  |     if (ui->lineName->text().isEmpty()) { | ||||||
|  |         QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter a cheat name.")); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if (ui->textCode->toPlainText().isEmpty()) { | ||||||
|  |         QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter the cheat code.")); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Check if the cheat lines are valid
 | ||||||
|  |     auto code_lines = ui->textCode->toPlainText().split("\n", QString::SkipEmptyParts); | ||||||
|  |     for (int i = 0; i < code_lines.size(); ++i) { | ||||||
|  |         Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString()); | ||||||
|  |         if (cheat_line.valid) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         auto answer = QMessageBox::warning( | ||||||
|  |             this, tr("Save Cheat"), | ||||||
|  |             tr("Cheat code line %1 is not valid.\nWould you like to ignore the error and continue?") | ||||||
|  |                 .arg(i + 1), | ||||||
|  |             QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||||
|  |         if (answer == QMessageBox::No) | ||||||
|  |             return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto cheat = std::make_shared<Cheats::GatewayCheat>(ui->lineName->text().toStdString(), | ||||||
|  |                                                         ui->textCode->toPlainText().toStdString(), | ||||||
|  |                                                         ui->textNotes->toPlainText().toStdString()); | ||||||
|  | 
 | ||||||
|  |     if (newly_created) { | ||||||
|  |         Core::System::GetInstance().CheatEngine().AddCheat(cheat); | ||||||
|  |         newly_created = false; | ||||||
|  |     } else { | ||||||
|  |         Core::System::GetInstance().CheatEngine().UpdateCheat(row, cheat); | ||||||
|  |     } | ||||||
|  |     Core::System::GetInstance().CheatEngine().SaveCheatFile(); | ||||||
|  | 
 | ||||||
|  |     int previous_row = ui->tableCheats->currentRow(); | ||||||
|  |     int previous_col = ui->tableCheats->currentColumn(); | ||||||
|  |     LoadCheats(); | ||||||
|  |     ui->tableCheats->setCurrentCell(previous_row, previous_col); | ||||||
|  | 
 | ||||||
|  |     edited = false; | ||||||
|  |     ui->buttonSave->setEnabled(false); | ||||||
|  |     ui->buttonAddCheat->setEnabled(true); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatDialog::closeEvent(QCloseEvent* event) { | ||||||
|  |     if (edited && !CheckSaveCheat()) { | ||||||
|  |         event->ignore(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     event->accept(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void CheatDialog::OnCancel() { | void CheatDialog::OnCancel() { | ||||||
|     close(); |     close(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CheatDialog::OnRowSelected(int row, int column) { | void CheatDialog::OnRowSelected(int row, int column) { | ||||||
|     ui->textDetails->setEnabled(true); |     if (row == last_row) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (edited && !CheckSaveCheat()) { | ||||||
|  |         ui->tableCheats->setCurrentCell(last_row, last_col); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (row < cheats.size()) { | ||||||
|  |         if (newly_created) { | ||||||
|  |             // Remove the newly created dummy item
 | ||||||
|  |             newly_created = false; | ||||||
|  |             ui->tableCheats->setRowCount(ui->tableCheats->rowCount() - 1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const auto& current_cheat = cheats[row]; | ||||||
|  |         ui->lineName->setText(QString::fromStdString(current_cheat->GetName())); | ||||||
|  |         ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); | ||||||
|  |         ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     edited = false; | ||||||
|  |     ui->buttonSave->setEnabled(false); | ||||||
|  |     ui->buttonDelete->setEnabled(true); | ||||||
|  |     ui->buttonAddCheat->setEnabled(true); | ||||||
|  |     ui->lineName->setEnabled(true); | ||||||
|  |     ui->textCode->setEnabled(true); | ||||||
|     ui->textNotes->setEnabled(true); |     ui->textNotes->setEnabled(true); | ||||||
|     const auto& current_cheat = Core::System::GetInstance().CheatEngine().GetCheats()[row]; | 
 | ||||||
|     ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); |     last_row = row; | ||||||
|     ui->textDetails->setPlainText(QString::fromStdString(current_cheat->ToString())); |     last_col = column; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CheatDialog::OnCheckChanged(int state) { | void CheatDialog::OnCheckChanged(int state) { | ||||||
|     const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender()); |     const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender()); | ||||||
|     int row = static_cast<int>(checkbox->property("row").toInt()); |     int row = static_cast<int>(checkbox->property("row").toInt()); | ||||||
|     Core::System::GetInstance().CheatEngine().GetCheats()[row]->SetEnabled(state); |     cheats[row]->SetEnabled(state); | ||||||
|  |     Core::System::GetInstance().CheatEngine().SaveCheatFile(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatDialog::OnTextEdited() { | ||||||
|  |     edited = true; | ||||||
|  |     ui->buttonSave->setEnabled(true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatDialog::OnDeleteCheat() { | ||||||
|  |     if (newly_created) { | ||||||
|  |         newly_created = false; | ||||||
|  |     } else { | ||||||
|  |         Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow()); | ||||||
|  |         Core::System::GetInstance().CheatEngine().SaveCheatFile(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     LoadCheats(); | ||||||
|  |     if (cheats.empty()) { | ||||||
|  |         ui->lineName->setText(""); | ||||||
|  |         ui->textCode->setPlainText(""); | ||||||
|  |         ui->textNotes->setPlainText(""); | ||||||
|  |         ui->lineName->setEnabled(false); | ||||||
|  |         ui->textCode->setEnabled(false); | ||||||
|  |         ui->textNotes->setEnabled(false); | ||||||
|  |         ui->buttonDelete->setEnabled(false); | ||||||
|  |         last_row = last_col = -1; | ||||||
|  |     } else { | ||||||
|  |         if (last_row >= ui->tableCheats->rowCount()) { | ||||||
|  |             last_row = ui->tableCheats->rowCount() - 1; | ||||||
|  |         } | ||||||
|  |         ui->tableCheats->setCurrentCell(last_row, last_col); | ||||||
|  | 
 | ||||||
|  |         const auto& current_cheat = cheats[last_row]; | ||||||
|  |         ui->lineName->setText(QString::fromStdString(current_cheat->GetName())); | ||||||
|  |         ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); | ||||||
|  |         ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     edited = false; | ||||||
|  |     ui->buttonSave->setEnabled(false); | ||||||
|  |     ui->buttonAddCheat->setEnabled(true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatDialog::OnAddCheat() { | ||||||
|  |     if (edited && !CheckSaveCheat()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     int row = ui->tableCheats->rowCount(); | ||||||
|  |     ui->tableCheats->setRowCount(row + 1); | ||||||
|  |     ui->tableCheats->setCurrentCell(row, 1); | ||||||
|  | 
 | ||||||
|  |     // create a dummy item
 | ||||||
|  |     ui->tableCheats->setItem(row, 1, new QTableWidgetItem(tr("[new cheat]"))); | ||||||
|  |     ui->tableCheats->setItem(row, 2, new QTableWidgetItem("")); | ||||||
|  |     ui->lineName->setText(""); | ||||||
|  |     ui->lineName->setPlaceholderText(tr("[new cheat]")); | ||||||
|  |     ui->textCode->setPlainText(""); | ||||||
|  |     ui->textNotes->setPlainText(""); | ||||||
|  |     ui->lineName->setEnabled(true); | ||||||
|  |     ui->textCode->setEnabled(true); | ||||||
|  |     ui->textNotes->setEnabled(true); | ||||||
|  |     ui->buttonSave->setEnabled(true); | ||||||
|  |     ui->buttonDelete->setEnabled(true); | ||||||
|  |     ui->buttonAddCheat->setEnabled(false); | ||||||
|  | 
 | ||||||
|  |     edited = false; | ||||||
|  |     newly_created = true; | ||||||
|  |     last_row = row; | ||||||
|  |     last_col = 1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,6 +7,10 @@ | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <QDialog> | #include <QDialog> | ||||||
| 
 | 
 | ||||||
|  | namespace Cheats { | ||||||
|  | class CheatBase; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| namespace Ui { | namespace Ui { | ||||||
| class CheatDialog; | class CheatDialog; | ||||||
| } // namespace Ui
 | } // namespace Ui
 | ||||||
|  | @ -19,12 +23,38 @@ public: | ||||||
|     ~CheatDialog(); |     ~CheatDialog(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<Ui::CheatDialog> ui; |     /**
 | ||||||
| 
 |      * Loads the cheats from the CheatEngine, and populates the table. | ||||||
|  |      */ | ||||||
|     void LoadCheats(); |     void LoadCheats(); | ||||||
| 
 | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Pops up a message box asking if the user wants to save the current cheat. | ||||||
|  |      * If the user selected Yes, attempts to save the current cheat. | ||||||
|  |      * @return true if the user selected No, or if the cheat was saved successfully | ||||||
|  |      *         false if the user selected Cancel, or if the user selected Yes but saving failed | ||||||
|  |      */ | ||||||
|  |     bool CheckSaveCheat(); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Saves the current cheat as the row-th cheat in the cheat list. | ||||||
|  |      * @return true if the cheat is saved successfully, false otherwise | ||||||
|  |      */ | ||||||
|  |     bool SaveCheat(int row); | ||||||
|  | 
 | ||||||
|  |     void closeEvent(QCloseEvent* event) override; | ||||||
|  | 
 | ||||||
| private slots: | private slots: | ||||||
|     void OnCancel(); |     void OnCancel(); | ||||||
|     void OnRowSelected(int row, int column); |     void OnRowSelected(int row, int column); | ||||||
|     void OnCheckChanged(int state); |     void OnCheckChanged(int state); | ||||||
|  |     void OnTextEdited(); | ||||||
|  |     void OnDeleteCheat(); | ||||||
|  |     void OnAddCheat(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::unique_ptr<Ui::CheatDialog> ui; | ||||||
|  |     std::vector<std::shared_ptr<Cheats::CheatBase>> cheats; | ||||||
|  |     bool edited = false, newly_created = false; | ||||||
|  |     int last_row = -1, last_col = -1; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -22,182 +22,192 @@ | ||||||
|   <property name="windowTitle"> |   <property name="windowTitle"> | ||||||
|    <string>Cheats</string> |    <string>Cheats</string> | ||||||
|   </property> |   </property> | ||||||
|   <widget class="QLabel" name="labelTitle"> |   <layout class="QVBoxLayout"> | ||||||
|    <property name="geometry"> |    <item> | ||||||
|     <rect> |     <layout class="QHBoxLayout"> | ||||||
|      <x>10</x> |      <item> | ||||||
|      <y>10</y> |       <widget class="QLabel" name="labelTitle"> | ||||||
|      <width>300</width> |        <property name="font"> | ||||||
|      <height>31</height> |         <font> | ||||||
|     </rect> |          <pointsize>10</pointsize> | ||||||
|    </property> |         </font> | ||||||
|    <property name="font"> |  | ||||||
|     <font> |  | ||||||
|      <pointsize>10</pointsize> |  | ||||||
|     </font> |  | ||||||
|    </property> |  | ||||||
|    <property name="text"> |  | ||||||
|     <string>Title ID:</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="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="editTriggers"> |  | ||||||
|        <set>QAbstractItemView::NoEditTriggers</set> |  | ||||||
|       </property> |  | ||||||
|       <property name="selectionBehavior"> |  | ||||||
|        <enum>QAbstractItemView::SelectRows</enum> |  | ||||||
|       </property> |  | ||||||
|       <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> |        </property> | ||||||
|       </column> |  | ||||||
|       <column> |  | ||||||
|        <property name="text"> |        <property name="text"> | ||||||
|         <string>Name</string> |         <string>Title ID:</string> | ||||||
|        </property> |        </property> | ||||||
|       </column> |       </widget> | ||||||
|       <column> |      </item> | ||||||
|  |      <item> | ||||||
|  |       <spacer> | ||||||
|  |        <property name="orientation"> | ||||||
|  |         <enum>Qt::Horizontal</enum> | ||||||
|  |        </property> | ||||||
|  |       </spacer> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QPushButton" name="buttonAddCheat"> | ||||||
|        <property name="text"> |        <property name="text"> | ||||||
|         <string>Type</string> |         <string>Add Cheat</string> | ||||||
|        </property> |        </property> | ||||||
|       </column> |       </widget> | ||||||
|      </widget> |      </item> | ||||||
|     </item> |     </layout> | ||||||
|    </layout> |    </item> | ||||||
|   </widget> |    <item> | ||||||
|   <widget class="QLabel" name="labelAvailableCheats"> |     <layout class="QHBoxLayout"> | ||||||
|    <property name="geometry"> |      <item> | ||||||
|     <rect> |       <layout class="QVBoxLayout"> | ||||||
|      <x>10</x> |        <item> | ||||||
|      <y>60</y> |         <widget class="QLabel" name="labelAvailableCheats"> | ||||||
|      <width>121</width> |          <property name="geometry"> | ||||||
|      <height>16</height> |           <rect> | ||||||
|     </rect> |            <x>10</x> | ||||||
|    </property> |            <y>60</y> | ||||||
|    <property name="text"> |            <width>121</width> | ||||||
|     <string>Available Cheats:</string> |            <height>16</height> | ||||||
|    </property> |           </rect> | ||||||
|   </widget> |          </property> | ||||||
|   <widget class="QWidget" name="verticalLayoutWidget_2"> |          <property name="text"> | ||||||
|    <property name="geometry"> |           <string>Available Cheats:</string> | ||||||
|     <rect> |          </property> | ||||||
|      <x>580</x> |         </widget> | ||||||
|      <y>440</y> |        </item> | ||||||
|      <width>271</width> |        <item> | ||||||
|      <height>111</height> |         <widget class="QTableWidget" name="tableCheats"> | ||||||
|     </rect> |          <property name="editTriggers"> | ||||||
|    </property> |           <set>QAbstractItemView::NoEditTriggers</set> | ||||||
|    <layout class="QVBoxLayout" name="verticalLayout_2"> |          </property> | ||||||
|     <item> |          <property name="selectionBehavior"> | ||||||
|      <widget class="QPlainTextEdit" name="textNotes"> |           <enum>QAbstractItemView::SelectRows</enum> | ||||||
|       <property name="readOnly"> |          </property> | ||||||
|        <bool>true</bool> |          <property name="selectionMode"> | ||||||
|       </property> |           <enum>QAbstractItemView::SingleSelection</enum> | ||||||
|      </widget> |          </property> | ||||||
|     </item> |          <property name="showGrid"> | ||||||
|    </layout> |           <bool>false</bool> | ||||||
|   </widget> |          </property> | ||||||
|   <widget class="QLabel" name="labelNotes"> |          <property name="columnCount"> | ||||||
|    <property name="geometry"> |           <number>3</number> | ||||||
|     <rect> |          </property> | ||||||
|      <x>580</x> |          <attribute name="horizontalHeaderVisible"> | ||||||
|      <y>420</y> |           <bool>true</bool> | ||||||
|      <width>111</width> |          </attribute> | ||||||
|      <height>16</height> |          <attribute name="verticalHeaderVisible"> | ||||||
|     </rect> |           <bool>false</bool> | ||||||
|    </property> |          </attribute> | ||||||
|    <property name="text"> |          <column> | ||||||
|     <string>Notes:</string> |           <property name="text"> | ||||||
|    </property> |            <string/> | ||||||
|   </widget> |           </property> | ||||||
|   <widget class="QWidget" name="verticalLayoutWidget_3"> |          </column> | ||||||
|    <property name="geometry"> |          <column> | ||||||
|     <rect> |           <property name="text"> | ||||||
|      <x>580</x> |            <string>Name</string> | ||||||
|      <y>80</y> |           </property> | ||||||
|      <width>271</width> |          </column> | ||||||
|      <height>311</height> |          <column> | ||||||
|     </rect> |           <property name="text"> | ||||||
|    </property> |            <string>Type</string> | ||||||
|    <layout class="QVBoxLayout" name="verticalLayout_3"> |           </property> | ||||||
|     <item> |          </column> | ||||||
|      <widget class="QPlainTextEdit" name="textDetails"> |         </widget> | ||||||
|       <property name="readOnly"> |        </item> | ||||||
|        <bool>true</bool> |       </layout> | ||||||
|       </property> |      </item> | ||||||
|      </widget> |      <item> | ||||||
|     </item> |       <layout class="QVBoxLayout"> | ||||||
|    </layout> |        <item> | ||||||
|   </widget> |         <layout class="QHBoxLayout"> | ||||||
|   <widget class="QLabel" name="labelDetails"> |          <item> | ||||||
|    <property name="geometry"> |           <spacer> | ||||||
|     <rect> |            <property name="orientation"> | ||||||
|      <x>580</x> |             <enum>Qt::Horizontal</enum> | ||||||
|      <y>60</y> |            </property> | ||||||
|      <width>55</width> |           </spacer> | ||||||
|      <height>16</height> |          </item> | ||||||
|     </rect> |          <item> | ||||||
|    </property> |           <widget class="QPushButton" name="buttonSave"> | ||||||
|    <property name="text"> |            <property name="text"> | ||||||
|     <string>Code:</string> |             <string>Save</string> | ||||||
|    </property> |            </property> | ||||||
|   </widget> |            <property name="enabled"> | ||||||
|  |             <bool>false</bool> | ||||||
|  |            </property> | ||||||
|  |           </widget> | ||||||
|  |          </item> | ||||||
|  |          <item> | ||||||
|  |           <widget class="QPushButton" name="buttonDelete"> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>Delete</string> | ||||||
|  |            </property> | ||||||
|  |            <property name="enabled"> | ||||||
|  |             <bool>false</bool> | ||||||
|  |            </property> | ||||||
|  |           </widget> | ||||||
|  |          </item> | ||||||
|  |         </layout> | ||||||
|  |        </item> | ||||||
|  |        <item> | ||||||
|  |         <layout class="QVBoxLayout"> | ||||||
|  |          <item> | ||||||
|  |           <layout class="QHBoxLayout"> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QLabel" name="labelName"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Name:</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QLineEdit" name="lineName"/> | ||||||
|  |            </item> | ||||||
|  |           </layout> | ||||||
|  |          </item> | ||||||
|  |          <item> | ||||||
|  |           <widget class="QLabel" name="labelNotes"> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>Notes:</string> | ||||||
|  |            </property> | ||||||
|  |           </widget> | ||||||
|  |          </item> | ||||||
|  |          <item> | ||||||
|  |           <widget class="QPlainTextEdit" name="textNotes"/> | ||||||
|  |          </item> | ||||||
|  |          <item> | ||||||
|  |           <widget class="QLabel" name="labelCode"> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>Code:</string> | ||||||
|  |            </property> | ||||||
|  |           </widget> | ||||||
|  |          </item> | ||||||
|  |          <item> | ||||||
|  |           <widget class="QPlainTextEdit" name="textCode"/> | ||||||
|  |          </item> | ||||||
|  |         </layout> | ||||||
|  |        </item> | ||||||
|  |       </layout> | ||||||
|  |      </item> | ||||||
|  |     </layout> | ||||||
|  |    </item> | ||||||
|  |    <item> | ||||||
|  |     <layout class="QHBoxLayout"> | ||||||
|  |      <item> | ||||||
|  |       <spacer> | ||||||
|  |        <property name="orientation"> | ||||||
|  |         <enum>Qt::Horizontal</enum> | ||||||
|  |        </property> | ||||||
|  |       </spacer> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QPushButton" name="buttonClose"> | ||||||
|  |        <property name="text"> | ||||||
|  |         <string>Close</string> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |     </layout> | ||||||
|  |    </item> | ||||||
|  |   </layout> | ||||||
|  </widget> |  </widget> | ||||||
|  <resources/> |  <resources/> | ||||||
|  <connections/> |  <connections/> | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ namespace Cheats { | ||||||
| class CheatBase { | class CheatBase { | ||||||
| public: | public: | ||||||
|     virtual ~CheatBase(); |     virtual ~CheatBase(); | ||||||
|     virtual void Execute(Core::System& system) = 0; |     virtual void Execute(Core::System& system) const = 0; | ||||||
| 
 | 
 | ||||||
|     virtual bool IsEnabled() const = 0; |     virtual bool IsEnabled() const = 0; | ||||||
|     virtual void SetEnabled(bool enabled) = 0; |     virtual void SetEnabled(bool enabled) = 0; | ||||||
|  | @ -22,6 +22,7 @@ public: | ||||||
|     virtual std::string GetComments() const = 0; |     virtual std::string GetComments() const = 0; | ||||||
|     virtual std::string GetName() const = 0; |     virtual std::string GetName() const = 0; | ||||||
|     virtual std::string GetType() const = 0; |     virtual std::string GetType() const = 0; | ||||||
|  |     virtual std::string GetCode() const = 0; | ||||||
| 
 | 
 | ||||||
|     virtual std::string ToString() const = 0; |     virtual std::string ToString() const = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -2,8 +2,10 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <fstream> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
|  | #include "common/file_util.h" | ||||||
| #include "core/cheats/cheats.h" | #include "core/cheats/cheats.h" | ||||||
| #include "core/cheats/gateway_cheat.h" | #include "core/cheats/gateway_cheat.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
|  | @ -26,10 +28,54 @@ CheatEngine::~CheatEngine() { | ||||||
|     system.CoreTiming().UnscheduleEvent(event, 0); |     system.CoreTiming().UnscheduleEvent(event, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const std::vector<std::unique_ptr<CheatBase>>& CheatEngine::GetCheats() const { | std::vector<std::shared_ptr<CheatBase>> CheatEngine::GetCheats() const { | ||||||
|  |     std::shared_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||||
|     return cheats_list; |     return cheats_list; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void CheatEngine::AddCheat(const std::shared_ptr<CheatBase>& cheat) { | ||||||
|  |     std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||||
|  |     cheats_list.push_back(cheat); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatEngine::RemoveCheat(int index) { | ||||||
|  |     std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||||
|  |     if (index < 0 || index >= cheats_list.size()) { | ||||||
|  |         LOG_ERROR(Core_Cheats, "Invalid index {}", index); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     cheats_list.erase(cheats_list.begin() + index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatEngine::UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_cheat) { | ||||||
|  |     std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||||
|  |     if (index < 0 || index >= cheats_list.size()) { | ||||||
|  |         LOG_ERROR(Core_Cheats, "Invalid index {}", index); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     cheats_list[index] = new_cheat; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CheatEngine::SaveCheatFile() const { | ||||||
|  |     const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); | ||||||
|  |     const std::string filepath = fmt::format( | ||||||
|  |         "{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id); | ||||||
|  | 
 | ||||||
|  |     if (!FileUtil::IsDirectory(cheat_dir)) { | ||||||
|  |         FileUtil::CreateDir(cheat_dir); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::ofstream file; | ||||||
|  |     OpenFStream(file, filepath, std::ios_base::out); | ||||||
|  | 
 | ||||||
|  |     auto cheats = GetCheats(); | ||||||
|  |     for (const auto& cheat : cheats) { | ||||||
|  |         file << cheat->ToString(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file.flush(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void CheatEngine::LoadCheatFile() { | void CheatEngine::LoadCheatFile() { | ||||||
|     const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); |     const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); | ||||||
|     const std::string filepath = fmt::format( |     const std::string filepath = fmt::format( | ||||||
|  | @ -43,13 +89,19 @@ void CheatEngine::LoadCheatFile() { | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|     auto gateway_cheats = GatewayCheat::LoadFile(filepath); |     auto gateway_cheats = GatewayCheat::LoadFile(filepath); | ||||||
|     std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list)); |     { | ||||||
|  |         std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||||
|  |         std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list)); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CheatEngine::RunCallback([[maybe_unused]] u64 userdata, int cycles_late) { | void CheatEngine::RunCallback([[maybe_unused]] u64 userdata, int cycles_late) { | ||||||
|     for (auto& cheat : cheats_list) { |     { | ||||||
|         if (cheat->IsEnabled()) { |         std::shared_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||||
|             cheat->Execute(system); |         for (auto& cheat : cheats_list) { | ||||||
|  |             if (cheat->IsEnabled()) { | ||||||
|  |                 cheat->Execute(system); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event); |     system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <shared_mutex> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
|  | @ -25,12 +26,17 @@ class CheatEngine { | ||||||
| public: | public: | ||||||
|     explicit CheatEngine(Core::System& system); |     explicit CheatEngine(Core::System& system); | ||||||
|     ~CheatEngine(); |     ~CheatEngine(); | ||||||
|     const std::vector<std::unique_ptr<CheatBase>>& GetCheats() const; |     std::vector<std::shared_ptr<CheatBase>> GetCheats() const; | ||||||
|  |     void AddCheat(const std::shared_ptr<CheatBase>& cheat); | ||||||
|  |     void RemoveCheat(int index); | ||||||
|  |     void UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_cheat); | ||||||
|  |     void SaveCheatFile() const; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     void LoadCheatFile(); |     void LoadCheatFile(); | ||||||
|     void RunCallback(u64 userdata, int cycles_late); |     void RunCallback(u64 userdata, int cycles_late); | ||||||
|     std::vector<std::unique_ptr<CheatBase>> cheats_list; |     std::vector<std::shared_ptr<CheatBase>> cheats_list; | ||||||
|  |     mutable std::shared_mutex cheats_list_mutex; | ||||||
|     Core::TimingEventType* event; |     Core::TimingEventType* event; | ||||||
|     Core::System& system; |     Core::System& system; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -176,6 +176,7 @@ GatewayCheat::CheatLine::CheatLine(const std::string& line) { | ||||||
|         type = CheatType::Null; |         type = CheatType::Null; | ||||||
|         cheat_line = line; |         cheat_line = line; | ||||||
|         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); |         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); | ||||||
|  |         valid = false; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     try { |     try { | ||||||
|  | @ -193,6 +194,7 @@ GatewayCheat::CheatLine::CheatLine(const std::string& line) { | ||||||
|         type = CheatType::Null; |         type = CheatType::Null; | ||||||
|         cheat_line = line; |         cheat_line = line; | ||||||
|         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); |         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); | ||||||
|  |         valid = false; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -201,9 +203,23 @@ GatewayCheat::GatewayCheat(std::string name_, std::vector<CheatLine> cheat_lines | ||||||
|     : name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) { |     : name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comments_) | ||||||
|  |     : name(std::move(name_)), comments(std::move(comments_)) { | ||||||
|  | 
 | ||||||
|  |     std::vector<std::string> code_lines; | ||||||
|  |     Common::SplitString(code, '\n', code_lines); | ||||||
|  | 
 | ||||||
|  |     std::vector<CheatLine> temp_cheat_lines; | ||||||
|  |     for (std::size_t i = 0; i < code_lines.size(); ++i) { | ||||||
|  |         if (!code_lines[i].empty()) | ||||||
|  |             temp_cheat_lines.emplace_back(code_lines[i]); | ||||||
|  |     } | ||||||
|  |     cheat_lines = std::move(temp_cheat_lines); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| GatewayCheat::~GatewayCheat() = default; | GatewayCheat::~GatewayCheat() = default; | ||||||
| 
 | 
 | ||||||
| void GatewayCheat::Execute(Core::System& system) { | void GatewayCheat::Execute(Core::System& system) const { | ||||||
|     State state; |     State state; | ||||||
| 
 | 
 | ||||||
|     Memory::MemorySystem& memory = system.Memory(); |     Memory::MemorySystem& memory = system.Memory(); | ||||||
|  | @ -421,13 +437,28 @@ std::string GatewayCheat::GetType() const { | ||||||
|     return "Gateway"; |     return "Gateway"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::string GatewayCheat::GetCode() const { | ||||||
|  |     std::string result; | ||||||
|  |     for (const auto& line : cheat_lines) | ||||||
|  |         result += line.cheat_line + '\n'; | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A special marker used to keep track of enabled cheats
 | ||||||
|  | static constexpr char EnabledText[] = "*citra_enabled"; | ||||||
|  | 
 | ||||||
| std::string GatewayCheat::ToString() const { | std::string GatewayCheat::ToString() const { | ||||||
|     std::string result; |     std::string result; | ||||||
|     result += '[' + name + "]\n"; |     result += '[' + name + "]\n"; | ||||||
|     result += comments + '\n'; |     if (enabled) { | ||||||
|     for (const auto& line : cheat_lines) |         result += EnabledText; | ||||||
|         result += line.cheat_line + '\n'; |         result += '\n'; | ||||||
|     result += '\n'; |     } | ||||||
|  |     std::vector<std::string> comment_lines; | ||||||
|  |     Common::SplitString(comments, '\n', comment_lines); | ||||||
|  |     for (const auto& comment_line : comment_lines) | ||||||
|  |         result += "*" + comment_line + '\n'; | ||||||
|  |     result += GetCode() + '\n'; | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -443,6 +474,7 @@ std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string | ||||||
|     std::string comments; |     std::string comments; | ||||||
|     std::vector<CheatLine> cheat_lines; |     std::vector<CheatLine> cheat_lines; | ||||||
|     std::string name; |     std::string name; | ||||||
|  |     bool enabled = false; | ||||||
| 
 | 
 | ||||||
|     while (!file.eof()) { |     while (!file.eof()) { | ||||||
|         std::string line; |         std::string line; | ||||||
|  | @ -452,18 +484,25 @@ std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string | ||||||
|         if (line.length() >= 2 && line.front() == '[') { |         if (line.length() >= 2 && line.front() == '[') { | ||||||
|             if (!cheat_lines.empty()) { |             if (!cheat_lines.empty()) { | ||||||
|                 cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); |                 cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); | ||||||
|  |                 cheats.back()->SetEnabled(enabled); | ||||||
|  |                 enabled = false; | ||||||
|             } |             } | ||||||
|             name = line.substr(1, line.length() - 2); |             name = line.substr(1, line.length() - 2); | ||||||
|             cheat_lines.clear(); |             cheat_lines.clear(); | ||||||
|             comments.erase(); |             comments.erase(); | ||||||
|         } else if (!line.empty() && line.front() == '*') { |         } else if (!line.empty() && line.front() == '*') { | ||||||
|             comments += line.substr(1, line.length() - 1) + '\n'; |             if (line == EnabledText) { | ||||||
|  |                 enabled = true; | ||||||
|  |             } else { | ||||||
|  |                 comments += line.substr(1, line.length() - 1) + '\n'; | ||||||
|  |             } | ||||||
|         } else if (!line.empty()) { |         } else if (!line.empty()) { | ||||||
|             cheat_lines.emplace_back(std::move(line)); |             cheat_lines.emplace_back(std::move(line)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (!cheat_lines.empty()) { |     if (!cheat_lines.empty()) { | ||||||
|         cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); |         cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); | ||||||
|  |         cheats.back()->SetEnabled(enabled); | ||||||
|     } |     } | ||||||
|     return cheats; |     return cheats; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -50,12 +50,14 @@ public: | ||||||
|         u32 value; |         u32 value; | ||||||
|         u32 first; |         u32 first; | ||||||
|         std::string cheat_line; |         std::string cheat_line; | ||||||
|  |         bool valid = true; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     GatewayCheat(std::string name, std::vector<CheatLine> cheat_lines, std::string comments); |     GatewayCheat(std::string name, std::vector<CheatLine> cheat_lines, std::string comments); | ||||||
|  |     GatewayCheat(std::string name, std::string code, std::string comments); | ||||||
|     ~GatewayCheat(); |     ~GatewayCheat(); | ||||||
| 
 | 
 | ||||||
|     void Execute(Core::System& system) override; |     void Execute(Core::System& system) const override; | ||||||
| 
 | 
 | ||||||
|     bool IsEnabled() const override; |     bool IsEnabled() const override; | ||||||
|     void SetEnabled(bool enabled) override; |     void SetEnabled(bool enabled) override; | ||||||
|  | @ -63,6 +65,7 @@ public: | ||||||
|     std::string GetComments() const override; |     std::string GetComments() const override; | ||||||
|     std::string GetName() const override; |     std::string GetName() const override; | ||||||
|     std::string GetType() const override; |     std::string GetType() const override; | ||||||
|  |     std::string GetCode() const override; | ||||||
|     std::string ToString() const override; |     std::string ToString() const override; | ||||||
| 
 | 
 | ||||||
|     /// Gateway cheats look like:
 |     /// Gateway cheats look like:
 | ||||||
|  | @ -77,7 +80,7 @@ public: | ||||||
| private: | private: | ||||||
|     std::atomic<bool> enabled = false; |     std::atomic<bool> enabled = false; | ||||||
|     const std::string name; |     const std::string name; | ||||||
|     const std::vector<CheatLine> cheat_lines; |     std::vector<CheatLine> cheat_lines; | ||||||
|     const std::string comments; |     const std::string comments; | ||||||
| }; | }; | ||||||
| } // namespace Cheats
 | } // namespace Cheats
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue