Feature: Emulate Disney Infinity Base

Create, Load and Clear Infinity figures on an emulated base in the UI
This commit is contained in:
Joshua de Reeper
2023-01-25 10:51:02 +13:00
parent 88320f385d
commit f632f94645
18 changed files with 1554 additions and 21 deletions

View File

@ -234,6 +234,8 @@ add_executable(dolphin-emu
Host.h
HotkeyScheduler.cpp
HotkeyScheduler.h
InfinityBase/InfinityBaseWindow.cpp
InfinityBase/InfinityBaseWindow.h
Main.cpp
MainWindow.cpp
MainWindow.h

View File

@ -155,6 +155,7 @@
<ClCompile Include="GCMemcardManager.cpp" />
<ClCompile Include="Host.cpp" />
<ClCompile Include="HotkeyScheduler.cpp" />
<ClCompile Include="InfinityBase/InfinityBaseWindow.cpp" />
<ClCompile Include="Main.cpp" />
<ClCompile Include="MainWindow.cpp" />
<ClCompile Include="MenuBar.cpp" />
@ -350,6 +351,7 @@
<QtMoc Include="GCMemcardManager.h" />
<QtMoc Include="Host.h" />
<QtMoc Include="HotkeyScheduler.h" />
<QtMoc Include="InfinityBase/InfinityBaseWindow.h" />
<QtMoc Include="MainWindow.h" />
<QtMoc Include="MenuBar.h" />
<QtMoc Include="NetPlay\ChunkedProgressDialog.h" />

View File

@ -0,0 +1,321 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/InfinityBase/InfinityBaseWindow.h"
#include <QCheckBox>
#include <QComboBox>
#include <QCompleter>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QString>
#include <QVBoxLayout>
#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<u8, IOS::HLE::USB::INFINITY_NUM_BLOCKS * IOS::HLE::USB::INFINITY_BLOCK_SIZE> 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<int>::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<const QString&>::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);
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <string>
#include <QDialog>
#include <QVBoxLayout>
#include <QWidget>
#include "Core/Core.h"
#include "Core/IOS/USB/Emulated/Infinity.h"
class QCheckBox;
class QGroupBox;
class QLineEdit;
class InfinityBaseWindow : public QWidget
{
Q_OBJECT
public:
explicit InfinityBaseWindow(QWidget* parent = nullptr);
~InfinityBaseWindow() override;
protected:
std::array<QLineEdit*, 7> m_edit_figures;
private:
void CreateMainWindow();
void AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8 slot);
void OnEmulationStateChanged(Core::State state);
void EmulateBase(bool emulate);
void ClearFigure(u8 slot);
void LoadFigure(u8 slot);
void CreateFigure(u8 slot);
void LoadFigurePath(u8 slot, const QString& path);
QCheckBox* m_checkbox;
QGroupBox* m_group_figures;
};
class CreateFigureDialog : public QDialog
{
Q_OBJECT
public:
explicit CreateFigureDialog(QWidget* parent, u8 slot);
QString GetFilePath() const;
protected:
QString m_file_path;
};

View File

@ -95,6 +95,7 @@
#include "DolphinQt/GameList/GameList.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/HotkeyScheduler.h"
#include "DolphinQt/InfinityBase/InfinityBaseWindow.h"
#include "DolphinQt/MenuBar.h"
#include "DolphinQt/NKitWarningDialog.h"
#include "DolphinQt/NetPlay/NetPlayBrowser.h"
@ -540,6 +541,7 @@ void MainWindow::ConnectMenuBar()
connect(m_menu_bar, &MenuBar::BrowseNetPlay, this, &MainWindow::ShowNetPlayBrowser);
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
// Movie
@ -1325,7 +1327,7 @@ void MainWindow::ShowSkylanderPortal()
{
if (!m_skylander_window)
{
m_skylander_window = new SkylanderPortalWindow;
m_skylander_window = new SkylanderPortalWindow();
}
m_skylander_window->show();
@ -1333,6 +1335,18 @@ void MainWindow::ShowSkylanderPortal()
m_skylander_window->activateWindow();
}
void MainWindow::ShowInfinityBase()
{
if (!m_infinity_window)
{
m_infinity_window = new InfinityBaseWindow();
}
m_infinity_window->show();
m_infinity_window->raise();
m_infinity_window->activateWindow();
}
void MainWindow::StateLoad()
{
QString path =

View File

@ -30,6 +30,7 @@ class GBATASInputWindow;
class GCTASInputWindow;
class GraphicsWindow;
class HotkeyScheduler;
class InfinityBaseWindow;
class JITWidget;
class LogConfigWidget;
class LogWidget;
@ -161,6 +162,7 @@ private:
void ShowNetPlayBrowser();
void ShowFIFOPlayer();
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowMemcardManager();
void ShowResourcePackManager();
void ShowCheatsManager();
@ -225,6 +227,7 @@ private:
GraphicsWindow* m_graphics_window = nullptr;
FIFOPlayerWindow* m_fifo_window = nullptr;
SkylanderPortalWindow* m_skylander_window = nullptr;
InfinityBaseWindow* m_infinity_window = nullptr;
MappingWindow* m_hotkey_window = nullptr;
FreeLookWindow* m_freelook_window = nullptr;

View File

@ -224,7 +224,10 @@ void MenuBar::AddToolsMenu()
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
tools_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
auto* usb_device_menu = new QMenu(tr("Emulated USB Devices"), tools_menu);
usb_device_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
tools_menu->addMenu(usb_device_menu);
tools_menu->addSeparator();

View File

@ -90,6 +90,7 @@ signals:
void ShowCheatsManager();
void ShowResourcePackManager();
void ShowSkylanderPortal();
void ShowInfinityBase();
void ConnectWiiRemote(int id);
// Options