Qt: Implement "AR Codes"

This commit is contained in:
spycrab 2018-01-01 20:01:58 +00:00
parent a4be51109c
commit afa69a5876
7 changed files with 444 additions and 0 deletions

View file

@ -32,6 +32,8 @@ set(SRCS
Translation.cpp
WiiUpdate.cpp
WiiUpdate.h
Config/ARCodeEditor.cpp
Config/ARCodeWidget.cpp
Config/CheatWarningWidget.cpp
Config/ControllersWindow.cpp
Config/FilesystemWidget.cpp

View file

@ -0,0 +1,170 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/Config/ARCodeEditor.h"
#include <QDialogButtonBox>
#include <QFontDatabase>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QStringList>
#include <QTextEdit>
#include "Core/ARDecrypt.h"
ARCodeEditor::ARCodeEditor(ActionReplay::ARCode& ar) : m_code(ar)
{
CreateWidgets();
ConnectWidgets();
m_name_edit->setText(QString::fromStdString(ar.name));
QString s;
for (ActionReplay::AREntry& e : ar.ops)
{
s += QStringLiteral("%1 %2\n")
.arg(e.cmd_addr, 8, 16, QLatin1Char('0'))
.arg(e.value, 8, 16, QLatin1Char('0'));
}
m_code_edit->setText(s);
}
void ARCodeEditor::CreateWidgets()
{
m_name_edit = new QLineEdit;
m_code_edit = new QTextEdit;
m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
QGridLayout* grid_layout = new QGridLayout;
grid_layout->addWidget(new QLabel(tr("Name:")), 0, 0);
grid_layout->addWidget(m_name_edit, 0, 1);
grid_layout->addWidget(new QLabel(tr("Code:")), 1, 0);
grid_layout->addWidget(m_code_edit, 1, 1);
grid_layout->addWidget(m_button_box, 2, 1);
QFont monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
m_code_edit->setFont(monospace);
setLayout(grid_layout);
}
void ARCodeEditor::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::accepted, this, &ARCodeEditor::accept);
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void ARCodeEditor::accept()
{
std::vector<ActionReplay::AREntry> entries;
std::vector<std::string> encrypted_lines;
QStringList lines = m_code_edit->toPlainText().split(QStringLiteral("\n"));
for (int i = 0; i < lines.size(); i++)
{
QString line = lines[i];
if (line.isEmpty())
continue;
QStringList values = line.split(QStringLiteral(" "));
bool good = true;
u32 addr = 0;
u32 value = 0;
if (values.size() == 2)
{
addr = values[0].toUInt(&good, 16);
if (good)
value = values[1].toUInt(&good, 16);
if (good)
entries.push_back(ActionReplay::AREntry(addr, value));
}
else
{
QStringList blocks = line.split(QStringLiteral("-"));
if (blocks.size() == 3 && blocks[0].size() == 4 && blocks[1].size() == 4 &&
blocks[2].size() == 4)
{
encrypted_lines.emplace_back(blocks[0].toStdString() + blocks[1].toStdString() +
blocks[2].toStdString());
}
else
{
good = false;
}
}
if (!good)
{
auto result = QMessageBox::warning(
this, tr("Parsing Error"),
tr("Unable to parse line %1 of the entered AR code as a valid "
"encrypted or decrypted code. Make sure you typed it correctly.\n\n"
"Would you like to ignore this line and continue parsing?")
.arg(i + 1),
QMessageBox::Ok | QMessageBox::Abort);
if (result == QMessageBox::Abort)
return;
}
}
if (!encrypted_lines.empty())
{
if (!entries.empty())
{
auto result = QMessageBox::warning(
this, tr("Invalid Mixed Code"),
tr("This Action Replay code contains both encrypted and unencrypted lines; "
"you should check that you have entered it correctly.\n\n"
"Do you want to discard all unencrypted lines?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
// YES = Discard the unencrypted lines then decrypt the encrypted ones instead.
// NO = Discard the encrypted lines, keep the unencrypted ones
// CANCEL = Stop and let the user go back to editing
switch (result)
{
case QMessageBox::Yes:
entries.clear();
break;
case QMessageBox::No:
encrypted_lines.clear();
break;
case QMessageBox::Cancel:
return;
default:
break;
}
}
ActionReplay::DecryptARCode(encrypted_lines, &entries);
}
if (entries.empty())
{
QMessageBox::critical(this, tr("Error"),
tr("The resulting decrypted AR code doesn't contain any lines."));
return;
}
m_code.name = m_name_edit->text().toStdString();
m_code.ops = std::move(entries);
m_code.active = true;
m_code.user_defined = true;
QDialog::accept();
}

View file

@ -0,0 +1,29 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/ActionReplay.h"
#include <QDialog>
class QDialogButtonBox;
class QLineEdit;
class QTextEdit;
class ARCodeEditor : public QDialog
{
public:
explicit ARCodeEditor(ActionReplay::ARCode& ar);
private:
void CreateWidgets();
void ConnectWidgets();
void accept() override;
QLineEdit* m_name_edit;
QTextEdit* m_code_edit;
QDialogButtonBox* m_button_box;
ActionReplay::ARCode& m_code;
};

View file

@ -0,0 +1,177 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/Config/ARCodeWidget.h"
#include <QButtonGroup>
#include <QHBoxLayout>
#include <QListWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Core/ActionReplay.h"
#include "Core/ConfigManager.h"
#include "DolphinQt2/Config/ARCodeEditor.h"
#include "DolphinQt2/Config/CheatWarningWidget.h"
#include "DolphinQt2/GameList/GameFile.h"
ARCodeWidget::ARCodeWidget(const GameFile& game)
: m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision())
{
CreateWidgets();
ConnectWidgets();
IniFile game_ini_local;
// We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI
// will always be stored in GS/${GAMEID}.ini
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision);
m_ar_codes = ActionReplay::LoadCodes(game_ini_default, game_ini_local);
UpdateList();
OnSelectionChanged();
}
void ARCodeWidget::CreateWidgets()
{
m_warning = new CheatWarningWidget(m_game_id);
m_code_list = new QListWidget;
m_code_add = new QPushButton(tr("Add New Code..."));
m_code_edit = new QPushButton(tr("Edit Code..."));
m_code_remove = new QPushButton(tr("Remove Code"));
auto* button_layout = new QHBoxLayout;
button_layout->addWidget(m_code_add);
button_layout->addWidget(m_code_edit);
button_layout->addWidget(m_code_remove);
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(m_warning);
layout->addWidget(m_code_list);
layout->addLayout(button_layout);
setLayout(layout);
}
void ARCodeWidget::ConnectWidgets()
{
connect(m_warning, &CheatWarningWidget::OpenCheatEnableSettings, this,
&ARCodeWidget::OpenGeneralSettings);
connect(m_code_list, &QListWidget::itemChanged, this, &ARCodeWidget::OnItemChanged);
connect(m_code_list, &QListWidget::itemSelectionChanged, this, &ARCodeWidget::OnSelectionChanged);
connect(m_code_add, &QPushButton::pressed, this, &ARCodeWidget::OnCodeAddPressed);
connect(m_code_edit, &QPushButton::pressed, this, &ARCodeWidget::OnCodeEditPressed);
connect(m_code_remove, &QPushButton::pressed, this, &ARCodeWidget::OnCodeRemovePressed);
}
void ARCodeWidget::OnItemChanged(QListWidgetItem* item)
{
m_ar_codes[m_code_list->row(item)].active = (item->checkState() == Qt::Checked);
SaveCodes();
}
void ARCodeWidget::OnSelectionChanged()
{
auto items = m_code_list->selectedItems();
if (items.empty())
return;
const auto* selected = items[0];
bool user_defined = m_ar_codes[m_code_list->row(selected)].user_defined;
m_code_remove->setEnabled(user_defined);
m_code_edit->setText(user_defined ? tr("Edit Code...") : tr("Clone and Edit Code..."));
}
void ARCodeWidget::UpdateList()
{
m_code_list->clear();
for (const auto& ar : m_ar_codes)
{
auto* item = new QListWidgetItem(QString::fromStdString(ar.name)
.replace(QStringLiteral("&lt;"), QStringLiteral("<"))
.replace(QStringLiteral("&gt;"), QStringLiteral(">")));
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
item->setCheckState(ar.active ? Qt::Checked : Qt::Unchecked);
m_code_list->addItem(item);
}
}
void ARCodeWidget::SaveCodes()
{
IniFile game_ini_local;
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
ActionReplay::SaveCodes(&game_ini_local, m_ar_codes);
game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
}
void ARCodeWidget::OnCodeAddPressed()
{
ActionReplay::ARCode ar;
ARCodeEditor ed(ar);
ed.exec();
m_ar_codes.push_back(std::move(ar));
UpdateList();
SaveCodes();
}
void ARCodeWidget::OnCodeEditPressed()
{
auto items = m_code_list->selectedItems();
if (items.empty())
return;
const auto* selected = items[0];
auto& current_ar = m_ar_codes[m_code_list->row(selected)];
bool user_defined = current_ar.user_defined;
ActionReplay::ARCode ar = current_ar;
ARCodeEditor ed(user_defined ? current_ar : ar);
ed.exec();
if (!user_defined)
m_ar_codes.push_back(ar);
SaveCodes();
UpdateList();
}
void ARCodeWidget::OnCodeRemovePressed()
{
auto items = m_code_list->selectedItems();
if (items.empty())
return;
const auto* selected = items[0];
m_ar_codes.erase(m_ar_codes.begin() + m_code_list->row(selected));
SaveCodes();
UpdateList();
m_code_remove->setEnabled(false);
}

View file

@ -0,0 +1,55 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QWidget>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/ActionReplay.h"
class CheatWarningWidget;
class GameFile;
class QLabel;
class QListWidget;
class QListWidgetItem;
class QPushButton;
class ARCodeWidget : public QWidget
{
Q_OBJECT
public:
explicit ARCodeWidget(const GameFile& game);
signals:
void OpenGeneralSettings();
private:
void OnSelectionChanged();
void OnItemChanged(QListWidgetItem* item);
void CreateWidgets();
void ConnectWidgets();
void UpdateList();
void SaveCodes();
void OnCodeAddPressed();
void OnCodeEditPressed();
void OnCodeRemovePressed();
const GameFile& m_game;
std::string m_game_id;
u8 m_game_revision;
CheatWarningWidget* m_warning;
QListWidget* m_code_list;
QPushButton* m_code_add;
QPushButton* m_code_edit;
QPushButton* m_code_remove;
std::vector<ActionReplay::ARCode> m_ar_codes;
};

View file

@ -6,6 +6,7 @@
#include <QTabWidget>
#include <QVBoxLayout>
#include "DolphinQt2/Config/ARCodeWidget.h"
#include "DolphinQt2/Config/FilesystemWidget.h"
#include "DolphinQt2/Config/GeckoCodeWidget.h"
#include "DolphinQt2/Config/InfoWidget.h"
@ -20,12 +21,16 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
QTabWidget* tab_widget = new QTabWidget(this);
InfoWidget* info = new InfoWidget(game);
ARCodeWidget* ar = new ARCodeWidget(game);
GeckoCodeWidget* gecko = new GeckoCodeWidget(game);
connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this,
&PropertiesDialog::OpenGeneralSettings);
connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings);
tab_widget->addTab(info, tr("Info"));
tab_widget->addTab(ar, tr("AR Codes"));
tab_widget->addTab(gecko, tr("Gecko Codes"));
if (DiscIO::IsDisc(game.GetPlatformID()))

View file

@ -60,6 +60,8 @@
<!--NOTE: When adding moc'd files, you must list outputs in the ClCompile ItemGroup too!-->
<ItemGroup>
<QtMoc Include="AboutDialog.h" />
<QtMoc Include="Config\ARCodeEditor.h" />
<QtMoc Include="Config\ARCodeWidget.h" />
<QtMoc Include="Config\CheatWarningWidget.h" />
<QtMoc Include="Config\ControllersWindow.h" />
<QtMoc Include="Config\FilesystemWidget.h" />
@ -112,6 +114,8 @@
</ItemGroup>
<!--TODO figure out how to get QtMoc to add outputs to ClCompile's inputs...-->
<ItemGroup>
<ClCompile Include="$(QtMocOutPrefix)ARCodeEditor.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ARCodeWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AboutDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AdvancedPane.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AudioPane.cpp" />
@ -162,6 +166,8 @@
<ClCompile Include="$(QtMocOutPrefix)SoftwareRendererWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ToolBar.cpp" />
<ClCompile Include="AboutDialog.cpp" />
<ClCompile Include="Config\ARCodeEditor.cpp" />
<ClCompile Include="Config\ARCodeWidget.cpp" />
<ClCompile Include="Config\CheatWarningWidget.cpp" />
<ClCompile Include="Config\ControllersWindow.cpp" />
<ClCompile Include="Config\FilesystemWidget.cpp" />