mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #11546 from deReeperJosh/disneyinfinitybase
Feature: Emulate Infinity Base
This commit is contained in:
@@ -405,6 +405,8 @@ add_library(core
|
||||
IOS/USB/Bluetooth/WiimoteHIDAttr.h
|
||||
IOS/USB/Common.cpp
|
||||
IOS/USB/Common.h
|
||||
IOS/USB/Emulated/Infinity.cpp
|
||||
IOS/USB/Emulated/Infinity.h
|
||||
IOS/USB/Emulated/Skylander.cpp
|
||||
IOS/USB/Emulated/Skylander.h
|
||||
IOS/USB/Host.cpp
|
||||
|
||||
@@ -555,6 +555,9 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
|
||||
const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
|
||||
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
|
||||
|
||||
const Info<bool> MAIN_EMULATE_INFINITY_BASE{
|
||||
{System::Main, "EmulatedUSBDevices", "EmulateInfinityBase"}, false};
|
||||
|
||||
// The reason we need this function is because some memory card code
|
||||
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
|
||||
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
|
||||
|
||||
@@ -344,6 +344,7 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
|
||||
// Main.EmulatedUSBDevices
|
||||
|
||||
extern const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL;
|
||||
extern const Info<bool> MAIN_EMULATE_INFINITY_BASE;
|
||||
|
||||
// GameCube path utility functions
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
constexpr u8 INFINITY_NUM_BLOCKS = 0x14;
|
||||
constexpr u8 INFINITY_BLOCK_SIZE = 0x10;
|
||||
|
||||
struct InfinityFigure final
|
||||
{
|
||||
void Save();
|
||||
|
||||
File::IOFile inf_file;
|
||||
std::array<u8, INFINITY_NUM_BLOCKS * INFINITY_BLOCK_SIZE> data{};
|
||||
bool present = false;
|
||||
u8 order_added = 255;
|
||||
};
|
||||
|
||||
class InfinityUSB final : public Device
|
||||
{
|
||||
public:
|
||||
InfinityUSB(EmulationKernel& ios, const std::string& device_name);
|
||||
~InfinityUSB() override;
|
||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
|
||||
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
|
||||
bool Attach() override;
|
||||
bool AttachAndChangeInterface(u8 interface) override;
|
||||
int CancelTransfer(u8 endpoint) override;
|
||||
int ChangeInterface(u8 interface) override;
|
||||
int GetNumberOfAltSettings(u8 interface) override;
|
||||
int SetAltSetting(u8 alt_setting) override;
|
||||
int SubmitTransfer(std::unique_ptr<CtrlMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
||||
|
||||
private:
|
||||
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, const std::array<u8, 32>& data,
|
||||
u64 expected_time_us);
|
||||
|
||||
EmulationKernel& m_ios;
|
||||
u16 m_vid = 0;
|
||||
u16 m_pid = 0;
|
||||
u8 m_active_interface = 0;
|
||||
bool m_device_attached = false;
|
||||
DeviceDescriptor m_device_descriptor;
|
||||
std::vector<ConfigDescriptor> m_config_descriptor;
|
||||
std::vector<InterfaceDescriptor> m_interface_descriptor;
|
||||
std::vector<EndpointDescriptor> m_endpoint_descriptor;
|
||||
std::queue<std::array<u8, 32>> m_queries;
|
||||
std::queue<std::unique_ptr<IntrMessage>> m_response_list;
|
||||
};
|
||||
|
||||
class InfinityBase final
|
||||
{
|
||||
public:
|
||||
bool HasFigureBeenAddedRemoved() const;
|
||||
std::array<u8, 32> PopAddedRemovedResponse();
|
||||
void GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence);
|
||||
void WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
|
||||
u8 sequence);
|
||||
void DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void RemoveFigure(u8 position);
|
||||
// Returns Infinity Figure name based on data from in_file param
|
||||
std::string LoadFigure(const std::array<u8, INFINITY_NUM_BLOCKS * INFINITY_BLOCK_SIZE>& buf,
|
||||
File::IOFile in_file, u8 position);
|
||||
bool CreateFigure(const std::string& file_path, u32 character);
|
||||
static std::span<const std::pair<const char*, const u32>> GetFigureList();
|
||||
std::string FindFigure(u32 character) const;
|
||||
|
||||
protected:
|
||||
std::mutex m_infinity_mutex;
|
||||
std::array<InfinityFigure, 7> m_figures;
|
||||
|
||||
private:
|
||||
InfinityFigure& GetFigureByOrder(u8 order_added);
|
||||
std::array<u8, 16> GenerateInfinityFigureKey(const std::vector<u8>& sha1_data);
|
||||
std::array<u8, 16> GenerateBlankFigureData(u32 figure_num);
|
||||
u8 DeriveFigurePosition(u8 position);
|
||||
void GenerateSeed(u32 seed);
|
||||
u32 GetNext();
|
||||
u64 Scramble(u32 num_to_scramble, u32 garbage);
|
||||
u32 Descramble(u64 num_to_descramble);
|
||||
u8 GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const;
|
||||
|
||||
// These 4 variables are state variables used during the seeding and use of the random number
|
||||
// generator.
|
||||
u32 m_random_a;
|
||||
u32 m_random_b;
|
||||
u32 m_random_c;
|
||||
u32 m_random_d;
|
||||
|
||||
u8 m_figure_order = 0;
|
||||
std::queue<std::array<u8, 32>> m_figure_added_removed_response;
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/Infinity.h"
|
||||
#include "Core/IOS/USB/Emulated/Skylander.h"
|
||||
#include "Core/IOS/USB/LibusbDevice.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
@@ -140,13 +141,7 @@ bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks
|
||||
|
||||
auto usb_device =
|
||||
std::make_unique<USB::LibusbDevice>(GetEmulationKernel(), device, descriptor);
|
||||
if (!ShouldAddDevice(*usb_device))
|
||||
return true;
|
||||
|
||||
const u64 id = usb_device->GetId();
|
||||
new_devices.insert(id);
|
||||
if (AddDevice(std::move(usb_device)) || always_add_hooks)
|
||||
hooks.emplace(GetDeviceById(id), ChangeEvent::Inserted);
|
||||
CheckAndAddDevice(std::move(usb_device), new_devices, hooks, always_add_hooks);
|
||||
return true;
|
||||
});
|
||||
if (ret != LIBUSB_SUCCESS)
|
||||
@@ -194,14 +189,25 @@ void USBHost::AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks&
|
||||
{
|
||||
auto skylanderportal =
|
||||
std::make_unique<USB::SkylanderUSB>(GetEmulationKernel(), "Skylander Portal");
|
||||
if (ShouldAddDevice(*skylanderportal))
|
||||
CheckAndAddDevice(std::move(skylanderportal), new_devices, hooks, always_add_hooks);
|
||||
}
|
||||
if (Config::Get(Config::MAIN_EMULATE_INFINITY_BASE) && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto infinity_base = std::make_unique<USB::InfinityUSB>(GetEmulationKernel(), "Infinity Base");
|
||||
CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks);
|
||||
}
|
||||
}
|
||||
|
||||
void USBHost::CheckAndAddDevice(std::unique_ptr<USB::Device> device, std::set<u64>& new_devices,
|
||||
DeviceChangeHooks& hooks, bool always_add_hooks)
|
||||
{
|
||||
if (ShouldAddDevice(*device))
|
||||
{
|
||||
const u64 deviceid = device->GetId();
|
||||
new_devices.insert(deviceid);
|
||||
if (AddDevice(std::move(device)) || always_add_hooks)
|
||||
{
|
||||
const u64 skyid = skylanderportal->GetId();
|
||||
new_devices.insert(skyid);
|
||||
if (AddDevice(std::move(skylanderportal)) || always_add_hooks)
|
||||
{
|
||||
hooks.emplace(GetDeviceById(skyid), ChangeEvent::Inserted);
|
||||
}
|
||||
hooks.emplace(GetDeviceById(deviceid), ChangeEvent::Inserted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ private:
|
||||
void DispatchHooks(const DeviceChangeHooks& hooks);
|
||||
void AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
||||
bool always_add_hooks);
|
||||
void CheckAndAddDevice(std::unique_ptr<USB::Device> device, std::set<u64>& new_devices,
|
||||
DeviceChangeHooks& hooks, bool always_add_hooks);
|
||||
|
||||
bool m_has_initialised = false;
|
||||
LibusbUtils::Context m_context;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "IOS/USB/Emulated/Infinity.h"
|
||||
#include "IOS/USB/Emulated/Skylander.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
@@ -63,6 +64,7 @@ struct System::Impl
|
||||
GeometryShaderManager m_geometry_shader_manager;
|
||||
GPFifo::GPFifoManager m_gp_fifo;
|
||||
HSP::HSPManager m_hsp;
|
||||
IOS::HLE::USB::InfinityBase m_infinity_base;
|
||||
IOS::HLE::USB::SkylanderPortal m_skylander_portal;
|
||||
Memory::MemoryManager m_memory;
|
||||
MemoryInterface::MemoryInterfaceManager m_memory_interface;
|
||||
@@ -197,6 +199,11 @@ IOS::HLE::USB::SkylanderPortal& System::GetSkylanderPortal() const
|
||||
return m_impl->m_skylander_portal;
|
||||
}
|
||||
|
||||
IOS::HLE::USB::InfinityBase& System::GetInfinityBase() const
|
||||
{
|
||||
return m_impl->m_infinity_base;
|
||||
}
|
||||
|
||||
Memory::MemoryManager& System::GetMemory() const
|
||||
{
|
||||
return m_impl->m_memory;
|
||||
|
||||
@@ -57,7 +57,8 @@ class HSPManager;
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
class SkylanderPortal;
|
||||
};
|
||||
class InfinityBase;
|
||||
}; // namespace IOS::HLE::USB
|
||||
namespace Memory
|
||||
{
|
||||
class MemoryManager;
|
||||
@@ -138,6 +139,7 @@ public:
|
||||
Interpreter& GetInterpreter() const;
|
||||
JitInterface& GetJitInterface() const;
|
||||
IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const;
|
||||
IOS::HLE::USB::InfinityBase& GetInfinityBase() const;
|
||||
Memory::MemoryManager& GetMemory() const;
|
||||
MemoryInterface::MemoryInterfaceManager& GetMemoryInterface() const;
|
||||
PowerPC::MMU& GetMMU() const;
|
||||
|
||||
@@ -377,6 +377,7 @@
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Common.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Infinity.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylander.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Host.h" />
|
||||
<ClInclude Include="Core\IOS\USB\LibusbDevice.h" />
|
||||
@@ -1005,6 +1006,7 @@
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Infinity.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylander.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Host.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\LibusbDevice.cpp" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
@@ -348,6 +349,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" />
|
||||
|
||||
@@ -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(QStringLiteral("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")), 0);
|
||||
add_line(vbox_group);
|
||||
AddFigureSlot(vbox_group, QString(tr("Player One")), 1);
|
||||
add_line(vbox_group);
|
||||
AddFigureSlot(vbox_group, QString(tr("Player One Ability One")), 3);
|
||||
add_line(vbox_group);
|
||||
AddFigureSlot(vbox_group, QString(tr("Player One Ability Two")), 5);
|
||||
add_line(vbox_group);
|
||||
AddFigureSlot(vbox_group, QString(tr("Player Two")), 2);
|
||||
add_line(vbox_group);
|
||||
AddFigureSlot(vbox_group, QString(tr("Player Two Ability One")), 4);
|
||||
add_line(vbox_group);
|
||||
AddFigureSlot(vbox_group, QString(tr("Player Two Ability Two")), 6);
|
||||
|
||||
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] = 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]);
|
||||
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]->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]->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]->setText(QString::fromStdString(
|
||||
system.GetInfinityBase().LoadFigure(file_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;
|
||||
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 == 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));
|
||||
filterlist << QString::fromStdString(entry.first);
|
||||
if (first_entry == 0)
|
||||
{
|
||||
first_entry = figure;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 u32 char_number = edit_num->text().toULong(&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.empty())
|
||||
{
|
||||
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("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('/');
|
||||
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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ signals:
|
||||
void ShowCheatsManager();
|
||||
void ShowResourcePackManager();
|
||||
void ShowSkylanderPortal();
|
||||
void ShowInfinityBase();
|
||||
void ConnectWiiRemote(int id);
|
||||
|
||||
// Options
|
||||
|
||||
Reference in New Issue
Block a user