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.
 | ||||
| 
 | ||||
| #include <QCheckBox> | ||||
| #include <QMessageBox> | ||||
| #include <QTableWidgetItem> | ||||
| #include "citra_qt/cheats.h" | ||||
| #include "core/cheats/cheat_base.h" | ||||
| #include "core/cheats/cheats.h" | ||||
| #include "core/cheats/gateway_cheat.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "ui_cheats.h" | ||||
|  | @ -21,14 +23,23 @@ CheatDialog::CheatDialog(QWidget* parent) | |||
|     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->lineName->setEnabled(false); | ||||
|     ui->textCode->setEnabled(false); | ||||
|     ui->textNotes->setEnabled(false); | ||||
|     const auto game_id = fmt::format( | ||||
|         "{:016X}", Core::System::GetInstance().Kernel().GetCurrentProcess()->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->buttonAddCheat, &QPushButton::released, this, &CheatDialog::OnAddCheat); | ||||
|     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(); | ||||
| } | ||||
|  | @ -36,7 +47,7 @@ CheatDialog::CheatDialog(QWidget* parent) | |||
| CheatDialog::~CheatDialog() = default; | ||||
| 
 | ||||
| void CheatDialog::LoadCheats() { | ||||
|     const auto& cheats = Core::System::GetInstance().CheatEngine().GetCheats(); | ||||
|     cheats = Core::System::GetInstance().CheatEngine().GetCheats(); | ||||
| 
 | ||||
|     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() { | ||||
|     close(); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|     const auto& current_cheat = Core::System::GetInstance().CheatEngine().GetCheats()[row]; | ||||
|     ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); | ||||
|     ui->textDetails->setPlainText(QString::fromStdString(current_cheat->ToString())); | ||||
| 
 | ||||
|     last_row = row; | ||||
|     last_col = column; | ||||
| } | ||||
| 
 | ||||
| void CheatDialog::OnCheckChanged(int state) { | ||||
|     const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender()); | ||||
|     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 <QDialog> | ||||
| 
 | ||||
| namespace Cheats { | ||||
| class CheatBase; | ||||
| } | ||||
| 
 | ||||
| namespace Ui { | ||||
| class CheatDialog; | ||||
| } // namespace Ui
 | ||||
|  | @ -19,12 +23,38 @@ public: | |||
|     ~CheatDialog(); | ||||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<Ui::CheatDialog> ui; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Loads the cheats from the CheatEngine, and populates the table. | ||||
|      */ | ||||
|     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: | ||||
|     void OnCancel(); | ||||
|     void OnRowSelected(int row, int column); | ||||
|     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"> | ||||
|    <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>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/> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="labelTitle"> | ||||
|        <property name="font"> | ||||
|         <font> | ||||
|          <pointsize>10</pointsize> | ||||
|         </font> | ||||
|        </property> | ||||
|       </column> | ||||
|       <column> | ||||
|        <property name="text"> | ||||
|         <string>Name</string> | ||||
|         <string>Title ID:</string> | ||||
|        </property> | ||||
|       </column> | ||||
|       <column> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <spacer> | ||||
|        <property name="orientation"> | ||||
|         <enum>Qt::Horizontal</enum> | ||||
|        </property> | ||||
|       </spacer> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QPushButton" name="buttonAddCheat"> | ||||
|        <property name="text"> | ||||
|         <string>Type</string> | ||||
|         <string>Add Cheat</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"> | ||||
|       <property name="readOnly"> | ||||
|        <bool>true</bool> | ||||
|       </property> | ||||
|      </widget> | ||||
|     </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"> | ||||
|       <property name="readOnly"> | ||||
|        <bool>true</bool> | ||||
|       </property> | ||||
|      </widget> | ||||
|     </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>Code:</string> | ||||
|    </property> | ||||
|   </widget> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <layout class="QVBoxLayout"> | ||||
|        <item> | ||||
|         <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> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QTableWidget" name="tableCheats"> | ||||
|          <property name="editTriggers"> | ||||
|           <set>QAbstractItemView::NoEditTriggers</set> | ||||
|          </property> | ||||
|          <property name="selectionBehavior"> | ||||
|           <enum>QAbstractItemView::SelectRows</enum> | ||||
|          </property> | ||||
|          <property name="selectionMode"> | ||||
|           <enum>QAbstractItemView::SingleSelection</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> | ||||
|          </column> | ||||
|          <column> | ||||
|           <property name="text"> | ||||
|            <string>Name</string> | ||||
|           </property> | ||||
|          </column> | ||||
|          <column> | ||||
|           <property name="text"> | ||||
|            <string>Type</string> | ||||
|           </property> | ||||
|          </column> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </item> | ||||
|      <item> | ||||
|       <layout class="QVBoxLayout"> | ||||
|        <item> | ||||
|         <layout class="QHBoxLayout"> | ||||
|          <item> | ||||
|           <spacer> | ||||
|            <property name="orientation"> | ||||
|             <enum>Qt::Horizontal</enum> | ||||
|            </property> | ||||
|           </spacer> | ||||
|          </item> | ||||
|          <item> | ||||
|           <widget class="QPushButton" name="buttonSave"> | ||||
|            <property name="text"> | ||||
|             <string>Save</string> | ||||
|            </property> | ||||
|            <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> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ namespace Cheats { | |||
| class CheatBase { | ||||
| public: | ||||
|     virtual ~CheatBase(); | ||||
|     virtual void Execute(Core::System& system) = 0; | ||||
|     virtual void Execute(Core::System& system) const = 0; | ||||
| 
 | ||||
|     virtual bool IsEnabled() const = 0; | ||||
|     virtual void SetEnabled(bool enabled) = 0; | ||||
|  | @ -22,6 +22,7 @@ public: | |||
|     virtual std::string GetComments() const = 0; | ||||
|     virtual std::string GetName() const = 0; | ||||
|     virtual std::string GetType() const = 0; | ||||
|     virtual std::string GetCode() const = 0; | ||||
| 
 | ||||
|     virtual std::string ToString() const = 0; | ||||
| }; | ||||
|  |  | |||
|  | @ -2,8 +2,10 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <fstream> | ||||
| #include <functional> | ||||
| #include <fmt/format.h> | ||||
| #include "common/file_util.h" | ||||
| #include "core/cheats/cheats.h" | ||||
| #include "core/cheats/gateway_cheat.h" | ||||
| #include "core/core.h" | ||||
|  | @ -26,10 +28,54 @@ CheatEngine::~CheatEngine() { | |||
|     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; | ||||
| } | ||||
| 
 | ||||
| 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() { | ||||
|     const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); | ||||
|     const std::string filepath = fmt::format( | ||||
|  | @ -43,13 +89,19 @@ void CheatEngine::LoadCheatFile() { | |||
|         return; | ||||
| 
 | ||||
|     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) { | ||||
|     for (auto& cheat : cheats_list) { | ||||
|         if (cheat->IsEnabled()) { | ||||
|             cheat->Execute(system); | ||||
|     { | ||||
|         std::shared_lock<std::shared_mutex> lock(cheats_list_mutex); | ||||
|         for (auto& cheat : cheats_list) { | ||||
|             if (cheat->IsEnabled()) { | ||||
|                 cheat->Execute(system); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <shared_mutex> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
|  | @ -25,12 +26,17 @@ class CheatEngine { | |||
| public: | ||||
|     explicit CheatEngine(Core::System& system); | ||||
|     ~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: | ||||
|     void LoadCheatFile(); | ||||
|     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::System& system; | ||||
| }; | ||||
|  |  | |||
|  | @ -176,6 +176,7 @@ GatewayCheat::CheatLine::CheatLine(const std::string& line) { | |||
|         type = CheatType::Null; | ||||
|         cheat_line = line; | ||||
|         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); | ||||
|         valid = false; | ||||
|         return; | ||||
|     } | ||||
|     try { | ||||
|  | @ -193,6 +194,7 @@ GatewayCheat::CheatLine::CheatLine(const std::string& line) { | |||
|         type = CheatType::Null; | ||||
|         cheat_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_)) { | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| void GatewayCheat::Execute(Core::System& system) { | ||||
| void GatewayCheat::Execute(Core::System& system) const { | ||||
|     State state; | ||||
| 
 | ||||
|     Memory::MemorySystem& memory = system.Memory(); | ||||
|  | @ -421,13 +437,28 @@ std::string GatewayCheat::GetType() const { | |||
|     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 result; | ||||
|     result += '[' + name + "]\n"; | ||||
|     result += comments + '\n'; | ||||
|     for (const auto& line : cheat_lines) | ||||
|         result += line.cheat_line + '\n'; | ||||
|     result += '\n'; | ||||
|     if (enabled) { | ||||
|         result += EnabledText; | ||||
|         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; | ||||
| } | ||||
| 
 | ||||
|  | @ -443,6 +474,7 @@ std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string | |||
|     std::string comments; | ||||
|     std::vector<CheatLine> cheat_lines; | ||||
|     std::string name; | ||||
|     bool enabled = false; | ||||
| 
 | ||||
|     while (!file.eof()) { | ||||
|         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 (!cheat_lines.empty()) { | ||||
|                 cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); | ||||
|                 cheats.back()->SetEnabled(enabled); | ||||
|                 enabled = false; | ||||
|             } | ||||
|             name = line.substr(1, line.length() - 2); | ||||
|             cheat_lines.clear(); | ||||
|             comments.erase(); | ||||
|         } 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()) { | ||||
|             cheat_lines.emplace_back(std::move(line)); | ||||
|         } | ||||
|     } | ||||
|     if (!cheat_lines.empty()) { | ||||
|         cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); | ||||
|         cheats.back()->SetEnabled(enabled); | ||||
|     } | ||||
|     return cheats; | ||||
| } | ||||
|  |  | |||
|  | @ -50,12 +50,14 @@ public: | |||
|         u32 value; | ||||
|         u32 first; | ||||
|         std::string cheat_line; | ||||
|         bool valid = true; | ||||
|     }; | ||||
| 
 | ||||
|     GatewayCheat(std::string name, std::vector<CheatLine> cheat_lines, std::string comments); | ||||
|     GatewayCheat(std::string name, std::string code, std::string comments); | ||||
|     ~GatewayCheat(); | ||||
| 
 | ||||
|     void Execute(Core::System& system) override; | ||||
|     void Execute(Core::System& system) const override; | ||||
| 
 | ||||
|     bool IsEnabled() const override; | ||||
|     void SetEnabled(bool enabled) override; | ||||
|  | @ -63,6 +65,7 @@ public: | |||
|     std::string GetComments() const override; | ||||
|     std::string GetName() const override; | ||||
|     std::string GetType() const override; | ||||
|     std::string GetCode() const override; | ||||
|     std::string ToString() const override; | ||||
| 
 | ||||
|     /// Gateway cheats look like:
 | ||||
|  | @ -77,7 +80,7 @@ public: | |||
| private: | ||||
|     std::atomic<bool> enabled = false; | ||||
|     const std::string name; | ||||
|     const std::vector<CheatLine> cheat_lines; | ||||
|     std::vector<CheatLine> cheat_lines; | ||||
|     const std::string comments; | ||||
| }; | ||||
| } // namespace Cheats
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue