From f632f9464524c01dbfb2c93bc7f58ed5840e61a6 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Wed, 25 Jan 2023 10:51:02 +1300 Subject: Feature: Emulate Disney Infinity Base Create, Load and Clear Infinity figures on an emulated base in the UI --- .../DolphinQt/InfinityBase/InfinityBaseWindow.cpp | 321 +++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp (limited to 'Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp') diff --git a/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp b/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp new file mode 100644 index 0000000000..88c5dc6a3f --- /dev/null +++ b/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp @@ -0,0 +1,321 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/InfinityBase/InfinityBaseWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/IOFile.h" + +#include "Core/Config/MainSettings.h" +#include "Core/System.h" + +#include "DolphinQt/QtUtils/DolphinFileDialog.h" +#include "DolphinQt/Settings.h" + +// Qt is not guaranteed to keep track of file paths using native file pickers, so we use this +// static variable to ensure we open at the most recent figure file location +static QString s_last_figure_path; + +InfinityBaseWindow::InfinityBaseWindow(QWidget* parent) : QWidget(parent) +{ + setWindowTitle(tr("Infinity Manager")); + setObjectName(QString::fromStdString("infinity_manager")); + setMinimumSize(QSize(700, 200)); + + CreateMainWindow(); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + &InfinityBaseWindow::OnEmulationStateChanged); + + installEventFilter(this); + + OnEmulationStateChanged(Core::GetState()); +}; + +InfinityBaseWindow::~InfinityBaseWindow() = default; + +void InfinityBaseWindow::CreateMainWindow() +{ + auto* main_layout = new QVBoxLayout(); + + auto* checkbox_group = new QGroupBox(); + auto* checkbox_layout = new QHBoxLayout(); + checkbox_layout->setAlignment(Qt::AlignHCenter); + m_checkbox = new QCheckBox(tr("Emulate Infinity Base"), this); + m_checkbox->setChecked(Config::Get(Config::MAIN_EMULATE_INFINITY_BASE)); + connect(m_checkbox, &QCheckBox::toggled, [=](bool checked) { EmulateBase(checked); }); + checkbox_layout->addWidget(m_checkbox); + checkbox_group->setLayout(checkbox_layout); + main_layout->addWidget(checkbox_group); + + auto add_line = [](QVBoxLayout* vbox) { + auto* line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + vbox->addWidget(line); + }; + + m_group_figures = new QGroupBox(tr("Active Infinity Figures:")); + auto* vbox_group = new QVBoxLayout(); + auto* scroll_area = new QScrollArea(); + + AddFigureSlot(vbox_group, QString(tr("Play Set/Power Disc")), 0x01); + add_line(vbox_group); + AddFigureSlot(vbox_group, QString(tr("Player One")), 0x02); + add_line(vbox_group); + AddFigureSlot(vbox_group, QString(tr("Player One Ability One")), 0x04); + add_line(vbox_group); + AddFigureSlot(vbox_group, QString(tr("Player One Ability Two")), 0x06); + add_line(vbox_group); + AddFigureSlot(vbox_group, QString(tr("Player Two")), 0x03); + add_line(vbox_group); + AddFigureSlot(vbox_group, QString(tr("Player Two Ability One")), 0x05); + add_line(vbox_group); + AddFigureSlot(vbox_group, QString(tr("Player Two Ability Two")), 0x07); + + m_group_figures->setLayout(vbox_group); + scroll_area->setWidget(m_group_figures); + scroll_area->setWidgetResizable(true); + m_group_figures->setVisible(Config::Get(Config::MAIN_EMULATE_INFINITY_BASE)); + main_layout->addWidget(scroll_area); + setLayout(main_layout); +} + +void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8 slot) +{ + auto* hbox_infinity = new QHBoxLayout(); + + auto* label_skyname = new QLabel(name); + + auto* clear_btn = new QPushButton(tr("Clear")); + auto* create_btn = new QPushButton(tr("Create")); + auto* load_btn = new QPushButton(tr("Load")); + m_edit_figures[slot - 1] = new QLineEdit(); + m_edit_figures[slot - 1]->setEnabled(false); + m_edit_figures[slot - 1]->setText(tr("None")); + + connect(clear_btn, &QAbstractButton::clicked, this, [this, slot] { ClearFigure(slot); }); + connect(create_btn, &QAbstractButton::clicked, this, [this, slot] { CreateFigure(slot); }); + connect(load_btn, &QAbstractButton::clicked, this, [this, slot] { LoadFigure(slot); }); + + hbox_infinity->addWidget(label_skyname); + hbox_infinity->addWidget(m_edit_figures[slot - 1]); + hbox_infinity->addWidget(clear_btn); + hbox_infinity->addWidget(create_btn); + hbox_infinity->addWidget(load_btn); + + vbox_group->addLayout(hbox_infinity); +} + +void InfinityBaseWindow::ClearFigure(u8 slot) +{ + auto& system = Core::System::GetInstance(); + m_edit_figures[slot - 1]->setText(tr("None")); + + system.GetInfinityBase().RemoveFigure(slot); +} + +void InfinityBaseWindow::LoadFigure(u8 slot) +{ + const QString file_path = + DolphinFileDialog::getOpenFileName(this, tr("Select Figure File"), s_last_figure_path, + QStringLiteral("Infinity Figure (*.bin);;")); + if (file_path.isEmpty()) + { + return; + } + + s_last_figure_path = QFileInfo(file_path).absolutePath() + QLatin1Char('/'); + + m_edit_figures[slot - 1]->setText(QFileInfo(file_path).fileName()); + + LoadFigurePath(slot, file_path); +} + +void InfinityBaseWindow::CreateFigure(u8 slot) +{ + CreateFigureDialog create_dlg(this, slot); + if (create_dlg.exec() == CreateFigureDialog::Accepted) + { + LoadFigurePath(slot, create_dlg.GetFilePath()); + } +} + +void InfinityBaseWindow::LoadFigurePath(u8 slot, const QString& path) +{ + File::IOFile inf_file(path.toStdString(), "r+b"); + if (!inf_file) + { + QMessageBox::warning( + this, tr("Failed to open the Infinity file!"), + tr("Failed to open the Infinity file(%1)!\nFile may already be in use on the base.") + .arg(path), + QMessageBox::Ok); + return; + } + std::array file_data; + if (!inf_file.ReadBytes(file_data.data(), file_data.size())) + { + QMessageBox::warning(this, tr("Failed to read the Infinity file!"), + tr("Failed to read the Infinity file(%1)!\nFile was too small.").arg(path), + QMessageBox::Ok); + return; + } + + auto& system = Core::System::GetInstance(); + + system.GetInfinityBase().RemoveFigure(slot); + m_edit_figures[slot - 1]->setText(QString::fromStdString( + system.GetInfinityBase().LoadFigure(file_data.data(), std::move(inf_file), slot))); +} + +CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(parent) +{ + setWindowTitle(tr("Infinity Figure Creator")); + setObjectName(QStringLiteral("infinity_creator")); + setMinimumSize(QSize(500, 150)); + auto* layout = new QVBoxLayout; + + auto* combo_figlist = new QComboBox(); + QStringList filterlist; + u16 first_entry = 0; + for (const auto& entry : Core::System::GetInstance().GetInfinityBase().GetFigureList()) + { + const auto figure = entry.second; + // Only display entry if it is a piece appropriate for the slot + if ((slot == 0x01 && (figure.type == IOS::HLE::USB::InfinityFigureType::Playset || + figure.type == IOS::HLE::USB::InfinityFigureType::Item)) || + ((slot == 0x02 || slot == 0x03) && + figure.type == IOS::HLE::USB::InfinityFigureType::Character) || + ((slot == 0x04 || slot == 0x05 || slot == 0x06 || slot == 0x07) && + (figure.type == IOS::HLE::USB::InfinityFigureType::Ability_A || + figure.type == IOS::HLE::USB::InfinityFigureType::Ability_B))) + { + combo_figlist->addItem(QString::fromStdString(entry.first), QVariant(figure.figure_number)); + filterlist << QString::fromStdString(entry.first); + if (first_entry == 0) + { + first_entry = figure.figure_number; + } + } + } + combo_figlist->addItem(tr("--Unknown--"), QVariant(0xFFFF)); + combo_figlist->setEditable(true); + combo_figlist->setInsertPolicy(QComboBox::NoInsert); + + auto* co_compl = new QCompleter(filterlist, this); + co_compl->setCaseSensitivity(Qt::CaseInsensitive); + co_compl->setCompletionMode(QCompleter::PopupCompletion); + co_compl->setFilterMode(Qt::MatchContains); + combo_figlist->setCompleter(co_compl); + + layout->addWidget(combo_figlist); + + auto* line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + + auto* hbox_idvar = new QHBoxLayout(); + auto* label_id = new QLabel(tr("Figure Number:")); + auto* edit_num = new QLineEdit(QString::fromStdString(std::to_string(first_entry))); + auto* rxv = new QRegularExpressionValidator(QRegularExpression(QStringLiteral("\\d*")), this); + edit_num->setValidator(rxv); + hbox_idvar->addWidget(label_id); + hbox_idvar->addWidget(edit_num); + layout->addLayout(hbox_idvar); + + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->button(QDialogButtonBox::Ok)->setText(tr("Create")); + layout->addWidget(buttons); + + setLayout(layout); + + connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { + const u32 char_info = combo_figlist->itemData(index).toUInt(); + if (char_info != 0xFFFFFFFF) + { + edit_num->setText(QString::number(char_info)); + } + }); + + connect(buttons, &QDialogButtonBox::accepted, this, [=, this]() { + bool ok_char = false; + const u16 char_number = edit_num->text().toUShort(&ok_char); + if (!ok_char) + { + QMessageBox::warning(this, tr("Error converting value"), tr("Character entered is invalid!"), + QMessageBox::Ok); + return; + } + + QString predef_name = s_last_figure_path; + + auto& system = Core::System::GetInstance(); + const auto found_fig = system.GetInfinityBase().FindFigure(char_number); + if (found_fig != "") + { + predef_name += QString::fromStdString(std::string(found_fig) + ".bin"); + } + else + { + QString str = tr("Unknown(%1).bin"); + predef_name += str.arg(char_number); + } + + m_file_path = DolphinFileDialog::getSaveFileName(this, tr("Create Infinity File"), predef_name, + tr("Infinity Object (*.bin);;")); + if (m_file_path.isEmpty()) + { + return; + } + + if (!system.GetInfinityBase().CreateFigure(m_file_path.toStdString(), char_number)) + { + QMessageBox::warning(this, tr("Failed to create Infinity file!"), + tr("Failed to create Infinity file:\n%1").arg(m_file_path), + QMessageBox::Ok); + return; + } + s_last_figure_path = QFileInfo(m_file_path).absolutePath() + QLatin1Char('/'); + accept(); + }); + + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(co_compl, QOverload::of(&QCompleter::activated), + [=](const QString& text) { + combo_figlist->setCurrentText(text); + combo_figlist->setCurrentIndex(combo_figlist->findText(text)); + }); +} + +QString CreateFigureDialog::GetFilePath() const +{ + return m_file_path; +} + +void InfinityBaseWindow::EmulateBase(bool emulate) +{ + Config::SetBaseOrCurrent(Config::MAIN_EMULATE_INFINITY_BASE, emulate); + m_group_figures->setVisible(emulate); +} + +void InfinityBaseWindow::OnEmulationStateChanged(Core::State state) +{ + const bool running = state != Core::State::Uninitialized; + + m_checkbox->setEnabled(!running); +} -- cgit v1.2.3 From 529d0a1c63eecd19f6c415e925992bdd7f2d021a Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 17 Apr 2023 12:40:26 +1200 Subject: Infinity Base: Code tidyups and de-duplication --- .../DolphinQt/InfinityBase/InfinityBaseWindow.cpp | 62 +++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) (limited to 'Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp') diff --git a/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp b/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp index 88c5dc6a3f..630ed4cfd0 100644 --- a/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp +++ b/Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp @@ -31,7 +31,7 @@ static QString s_last_figure_path; InfinityBaseWindow::InfinityBaseWindow(QWidget* parent) : QWidget(parent) { setWindowTitle(tr("Infinity Manager")); - setObjectName(QString::fromStdString("infinity_manager")); + setObjectName(QStringLiteral("infinity_manager")); setMinimumSize(QSize(700, 200)); CreateMainWindow(); @@ -71,19 +71,19 @@ void InfinityBaseWindow::CreateMainWindow() auto* vbox_group = new QVBoxLayout(); auto* scroll_area = new QScrollArea(); - AddFigureSlot(vbox_group, QString(tr("Play Set/Power Disc")), 0x01); + AddFigureSlot(vbox_group, QString(tr("Play Set/Power Disc")), 0); add_line(vbox_group); - AddFigureSlot(vbox_group, QString(tr("Player One")), 0x02); + AddFigureSlot(vbox_group, QString(tr("Player One")), 1); add_line(vbox_group); - AddFigureSlot(vbox_group, QString(tr("Player One Ability One")), 0x04); + AddFigureSlot(vbox_group, QString(tr("Player One Ability One")), 3); add_line(vbox_group); - AddFigureSlot(vbox_group, QString(tr("Player One Ability Two")), 0x06); + AddFigureSlot(vbox_group, QString(tr("Player One Ability Two")), 5); add_line(vbox_group); - AddFigureSlot(vbox_group, QString(tr("Player Two")), 0x03); + AddFigureSlot(vbox_group, QString(tr("Player Two")), 2); add_line(vbox_group); - AddFigureSlot(vbox_group, QString(tr("Player Two Ability One")), 0x05); + AddFigureSlot(vbox_group, QString(tr("Player Two Ability One")), 4); add_line(vbox_group); - AddFigureSlot(vbox_group, QString(tr("Player Two Ability Two")), 0x07); + AddFigureSlot(vbox_group, QString(tr("Player Two Ability Two")), 6); m_group_figures->setLayout(vbox_group); scroll_area->setWidget(m_group_figures); @@ -102,16 +102,16 @@ void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8 auto* clear_btn = new QPushButton(tr("Clear")); auto* create_btn = new QPushButton(tr("Create")); auto* load_btn = new QPushButton(tr("Load")); - m_edit_figures[slot - 1] = new QLineEdit(); - m_edit_figures[slot - 1]->setEnabled(false); - m_edit_figures[slot - 1]->setText(tr("None")); + m_edit_figures[slot] = new QLineEdit(); + m_edit_figures[slot]->setEnabled(false); + m_edit_figures[slot]->setText(tr("None")); connect(clear_btn, &QAbstractButton::clicked, this, [this, slot] { ClearFigure(slot); }); connect(create_btn, &QAbstractButton::clicked, this, [this, slot] { CreateFigure(slot); }); connect(load_btn, &QAbstractButton::clicked, this, [this, slot] { LoadFigure(slot); }); hbox_infinity->addWidget(label_skyname); - hbox_infinity->addWidget(m_edit_figures[slot - 1]); + hbox_infinity->addWidget(m_edit_figures[slot]); hbox_infinity->addWidget(clear_btn); hbox_infinity->addWidget(create_btn); hbox_infinity->addWidget(load_btn); @@ -122,7 +122,7 @@ void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8 void InfinityBaseWindow::ClearFigure(u8 slot) { auto& system = Core::System::GetInstance(); - m_edit_figures[slot - 1]->setText(tr("None")); + m_edit_figures[slot]->setText(tr("None")); system.GetInfinityBase().RemoveFigure(slot); } @@ -139,7 +139,7 @@ void InfinityBaseWindow::LoadFigure(u8 slot) s_last_figure_path = QFileInfo(file_path).absolutePath() + QLatin1Char('/'); - m_edit_figures[slot - 1]->setText(QFileInfo(file_path).fileName()); + m_edit_figures[slot]->setText(QFileInfo(file_path).fileName()); LoadFigurePath(slot, file_path); } @@ -177,8 +177,8 @@ void InfinityBaseWindow::LoadFigurePath(u8 slot, const QString& path) auto& system = Core::System::GetInstance(); system.GetInfinityBase().RemoveFigure(slot); - m_edit_figures[slot - 1]->setText(QString::fromStdString( - system.GetInfinityBase().LoadFigure(file_data.data(), std::move(inf_file), slot))); + m_edit_figures[slot]->setText(QString::fromStdString( + system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file), slot))); } CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(parent) @@ -190,24 +190,22 @@ CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(paren auto* combo_figlist = new QComboBox(); QStringList filterlist; - u16 first_entry = 0; + u32 first_entry = 0; for (const auto& entry : Core::System::GetInstance().GetInfinityBase().GetFigureList()) { const auto figure = entry.second; // Only display entry if it is a piece appropriate for the slot - if ((slot == 0x01 && (figure.type == IOS::HLE::USB::InfinityFigureType::Playset || - figure.type == IOS::HLE::USB::InfinityFigureType::Item)) || - ((slot == 0x02 || slot == 0x03) && - figure.type == IOS::HLE::USB::InfinityFigureType::Character) || - ((slot == 0x04 || slot == 0x05 || slot == 0x06 || slot == 0x07) && - (figure.type == IOS::HLE::USB::InfinityFigureType::Ability_A || - figure.type == IOS::HLE::USB::InfinityFigureType::Ability_B))) + if ((slot == 0 && + ((figure > 0x1E8480 && figure < 0x2DC6BF) || (figure > 0x3D0900 && figure < 0x4C4B3F))) || + ((slot == 1 || slot == 2) && figure < 0x1E847F) || + ((slot == 3 || slot == 4 || slot == 5 || slot == 6) && + (figure > 0x2DC6C0 && figure < 0x3D08FF))) { - combo_figlist->addItem(QString::fromStdString(entry.first), QVariant(figure.figure_number)); + combo_figlist->addItem(QString::fromStdString(entry.first), QVariant(figure)); filterlist << QString::fromStdString(entry.first); if (first_entry == 0) { - first_entry = figure.figure_number; + first_entry = figure; } } } @@ -253,7 +251,7 @@ CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(paren connect(buttons, &QDialogButtonBox::accepted, this, [=, this]() { bool ok_char = false; - const u16 char_number = edit_num->text().toUShort(&ok_char); + const u32 char_number = edit_num->text().toULong(&ok_char); if (!ok_char) { QMessageBox::warning(this, tr("Error converting value"), tr("Character entered is invalid!"), @@ -265,7 +263,7 @@ CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(paren auto& system = Core::System::GetInstance(); const auto found_fig = system.GetInfinityBase().FindFigure(char_number); - if (found_fig != "") + if (!found_fig.empty()) { predef_name += QString::fromStdString(std::string(found_fig) + ".bin"); } @@ -284,9 +282,11 @@ CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(paren if (!system.GetInfinityBase().CreateFigure(m_file_path.toStdString(), char_number)) { - QMessageBox::warning(this, tr("Failed to create Infinity file!"), - tr("Failed to create Infinity file:\n%1").arg(m_file_path), - QMessageBox::Ok); + QMessageBox::warning( + this, tr("Failed to create Infinity file"), + tr("Blank figure creation failed at:\n%1, try again with a different character") + .arg(m_file_path), + QMessageBox::Ok); return; } s_last_figure_path = QFileInfo(m_file_path).absolutePath() + QLatin1Char('/'); -- cgit v1.2.3