// Copyright 2022 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later // DolphinQt code copied and modified for Dolphin from the RPCS3 Qt utility for Creating, Loading // and Clearing Skylanders #include "DolphinQt/SkylanderPortal/SkylanderPortalWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Common/IOFile.h" #include "Common/FileUtil.h" #include "Core/Config/MainSettings.h" #include "Core/IOS/USB/Emulated/Skylanders/Skylander.h" #include "Core/System.h" #include "DolphinQt/QtUtils/DolphinFileDialog.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" #include "SkylanderModifyDialog.h" SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent) { setWindowTitle(tr("Skylanders Manager")); setWindowIcon(Resources::GetAppIcon()); setObjectName(QStringLiteral("skylanders_manager")); setMinimumSize(QSize(650, 500)); m_only_show_collection = new QCheckBox(tr("Only Show Collection")); CreateMainWindow(); connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &SkylanderPortalWindow::OnEmulationStateChanged); installEventFilter(this); OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); connect(m_skylander_list, &QListWidget::itemSelectionChanged, this, &SkylanderPortalWindow::UpdateCurrentIDs); QDir skylanders_folder; // skylanders folder in user directory QString user_path = QString::fromStdString(File::GetUserPath(D_USER_IDX)) + QStringLiteral("Skylanders"); // first time initialize path in config if (Config::Get(Config::MAIN_SKYLANDERS_PATH).empty()) { Config::SetBase(Config::MAIN_SKYLANDERS_PATH, user_path.toStdString()); skylanders_folder = QDir(user_path); } else { skylanders_folder = QDir(QString::fromStdString(Config::Get(Config::MAIN_SKYLANDERS_PATH))); } if (!skylanders_folder.exists()) skylanders_folder.mkdir(skylanders_folder.path()); m_collection_path = QDir::toNativeSeparators(skylanders_folder.path()) + QDir::separator(); m_last_skylander_path = m_collection_path; m_path_edit->setText(m_collection_path); } SkylanderPortalWindow::~SkylanderPortalWindow() = default; // Window creation void SkylanderPortalWindow::CreateMainWindow() { auto* main_layout = new QVBoxLayout(); auto* select_layout = new QHBoxLayout; setMinimumWidth(770); // yes, that +1 on 755 is needed to avoid scroll bars on the element selection setMinimumHeight(756); // left and right widgets within window separated into own functions for readability select_layout->addLayout(CreateSlotLayout()); select_layout->addLayout(CreateFinderLayout()); select_layout->setStretch(0, 1); select_layout->setStretch(1, 2); main_layout->addLayout(select_layout); // create command buttons at bottom of window QBoxLayout* command_layout = new QHBoxLayout; m_command_buttons = new QFrame(); command_layout->setAlignment(Qt::AlignCenter); auto* create_btn = new QPushButton(tr("Customize")); auto* load_file_btn = new QPushButton(tr("Load File")); auto* clear_btn = new QPushButton(tr("Clear Slot")); auto* load_btn = new QPushButton(tr("Load Slot")); auto* modify_btn = new QPushButton(tr("Modify Slot")); connect(create_btn, &QAbstractButton::clicked, this, &SkylanderPortalWindow::CreateSkylanderAdvanced); connect(clear_btn, &QAbstractButton::clicked, this, [this] { ClearSlot(GetCurrentSlot()); }); connect(load_btn, &QAbstractButton::clicked, this, &SkylanderPortalWindow::LoadSelected); connect(load_file_btn, &QAbstractButton::clicked, this, &SkylanderPortalWindow::LoadFromFile); connect(modify_btn, &QAbstractButton::clicked, this, &SkylanderPortalWindow::ModifySkylander); command_layout->addWidget(create_btn); command_layout->addWidget(load_file_btn); command_layout->addWidget(clear_btn); command_layout->addWidget(load_btn); command_layout->addWidget(modify_btn); m_command_buttons->setLayout(command_layout); main_layout->addWidget(m_command_buttons); setLayout(main_layout); RefreshList(); m_skylander_list->setCurrentItem(m_skylander_list->item(0), QItemSelectionModel::Select); UpdateSlotNames(); } QVBoxLayout* SkylanderPortalWindow::CreateSlotLayout() { auto* slot_layout = new QVBoxLayout(); // WIDGET: Portal Enable Checkbox auto* checkbox_layout = new QVBoxLayout(); m_enabled_checkbox = new QCheckBox(tr("Emulate Skylander Portal"), this); m_enabled_checkbox->setChecked(Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL)); m_emulating = Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL); connect(m_enabled_checkbox, &QCheckBox::toggled, this, &SkylanderPortalWindow::EmulatePortal); checkbox_layout->addWidget(m_enabled_checkbox); slot_layout->addLayout(checkbox_layout); // WIDGET: Portal Slots // Helper function for creating slots auto add_line = [](QVBoxLayout* vbox) { auto* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); vbox->addWidget(line); }; // Create portal slots auto* portal_slots_group = new QGroupBox(tr("Portal Slots")); auto* portal_slots_layout = new QVBoxLayout(); m_group_skylanders = new QFrame(); auto* vbox_group = new QVBoxLayout(); auto* scroll_area = new QScrollArea(); for (auto i = 0; i < MAX_SKYLANDERS; i++) { if (i != 0) { add_line(vbox_group); } auto* hbox_skylander = new QHBoxLayout(); auto* label_skyname = new QLabel(tr("Skylander %1").arg(i + 1)); m_edit_skylanders[i] = new QLineEdit(); m_edit_skylanders[i]->setEnabled(false); QRadioButton* button = new QRadioButton; m_slot_radios[i] = button; button->setProperty("id", i); hbox_skylander->addWidget(button); hbox_skylander->addWidget(label_skyname); hbox_skylander->addWidget(m_edit_skylanders[i]); vbox_group->addLayout(hbox_skylander); } m_slot_radios[0]->setChecked(true); m_group_skylanders->setLayout(vbox_group); m_group_skylanders->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); scroll_area->setWidget(m_group_skylanders); scroll_area->setWidgetResizable(true); scroll_area->setFrameStyle(QFrame::NoFrame); m_group_skylanders->setVisible(Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL)); portal_slots_layout->addWidget(scroll_area); portal_slots_group->setLayout(portal_slots_layout); slot_layout->addWidget(portal_slots_group); return slot_layout; } QVBoxLayout* SkylanderPortalWindow::CreateFinderLayout() { auto* main_layout = new QVBoxLayout(); // WIDGET: Skylander List m_skylander_list = new QListWidget; connect(m_skylander_list, &QListWidget::itemDoubleClicked, this, &SkylanderPortalWindow::LoadSelected); auto* collection_layout = new QHBoxLayout(); // WIDGET: Skylander Collection Path collection_layout->addWidget(new QLabel(tr("Skylander Collection Path:"))); m_path_edit = new QLineEdit; collection_layout->addWidget(m_path_edit); m_path_select = new QPushButton(tr("Choose")); connect(m_path_edit, &QLineEdit::editingFinished, this, &SkylanderPortalWindow::OnCollectionPathChanged); connect(m_path_select, &QAbstractButton::clicked, this, &SkylanderPortalWindow::SelectCollectionPath); collection_layout->addWidget(m_path_select); main_layout->addLayout(collection_layout); auto* finder_layout = new QVBoxLayout(); // WIDGET: Search Bar auto* search_bar_layout = new QHBoxLayout; m_sky_search = new QLineEdit; m_sky_search->setClearButtonEnabled(true); connect(m_sky_search, &QLineEdit::textChanged, this, &SkylanderPortalWindow::RefreshList); search_bar_layout->addWidget(new QLabel(tr("Search:"))); search_bar_layout->addWidget(m_sky_search); finder_layout->addLayout(search_bar_layout); auto* finder_list_layout = new QHBoxLayout(); auto* search_filters_layout = new QVBoxLayout(); // WIDGET: Filter by Game auto* search_game_group = new QGroupBox(tr("Game")); auto* search_game_layout = new QVBoxLayout(); auto* search_checkbox_scroll_area = new QScrollArea(); search_checkbox_scroll_area->setContentsMargins(0, 0, 0, 0); search_checkbox_scroll_area->setFrameStyle(QFrame::NoFrame); auto* search_checkbox_group = new QFrame(); search_checkbox_group->setContentsMargins(0, 0, 0, 0); auto* search_checkbox_layout = new QVBoxLayout(); for (size_t i = 0; i < m_game_filters.size(); ++i) { QCheckBox* checkbox = new QCheckBox(this); checkbox->setChecked(true); connect(checkbox, &QCheckBox::toggled, this, &SkylanderPortalWindow::RefreshList); m_game_filters[i] = checkbox; search_checkbox_layout->addWidget(checkbox); } // i18n: Figures for the game Skylanders: Spyro's Adventure. The game has the same title in all // countries it was released in, except Japan, where it's named スカイランダーズ スパイロの大冒険. m_game_filters[GetGameID(Game::SpyrosAdv)]->setText(tr("Spyro's Adventure")); // i18n: Figures for the game Skylanders: Giants. The game has the same title in all countries // it was released in. It was not released in Japan. m_game_filters[GetGameID(Game::Giants)]->setText(tr("Giants")); // i18n: Figures for the game Skylanders: Swap Force. The game has the same title in all countries // it was released in. It was not released in Japan. m_game_filters[GetGameID(Game::SwapForce)]->setText(tr("Swap Force")); // i18n: Figures for the game Skylanders: Trap Team. The game has the same title in all countries // it was released in. It was not released in Japan. m_game_filters[GetGameID(Game::TrapTeam)]->setText(tr("Trap Team")); // i18n: Figures for the games Skylanders: SuperChargers (not available for the Wii) and // Skylanders: SuperChargers Racing (available for the Wii). The games have the same titles in // all countries they were released in. They were not released in Japan. m_game_filters[GetGameID(Game::Superchargers)]->setText(tr("SuperChargers")); search_checkbox_group->setLayout(search_checkbox_layout); search_checkbox_scroll_area->setWidget(search_checkbox_group); search_game_layout->addWidget(search_checkbox_scroll_area); search_game_group->setLayout(search_game_layout); search_game_group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); search_filters_layout->addWidget(search_game_group); // WIDGET: Filter by Element // i18n: Elements are a trait of Skylanders figures. For official translations of this term, // check the Skylanders SuperChargers manual at https://support.activision.com/manuals auto* search_element_group = new QGroupBox(tr("Element")); auto* search_element_layout = new QVBoxLayout(); auto* search_radio_scroll_area = new QScrollArea(); search_radio_scroll_area->setContentsMargins(0, 0, 0, 0); search_radio_scroll_area->setFrameStyle(QFrame::NoFrame); auto* search_radio_group = new QFrame(); search_radio_group->setContentsMargins(0, 0, 0, 0); auto* search_radio_layout = new QHBoxLayout(); auto* radio_layout_left = new QVBoxLayout(); auto* radio_layout_right = new QVBoxLayout(); for (int i = 0; i < NUM_SKYLANDER_ELEMENTS_RADIO; i++) { QRadioButton* radio = new QRadioButton(this); radio->setProperty("id", i); if (i == 0) { radio->setChecked(true); } connect(radio, &QRadioButton::toggled, this, &SkylanderPortalWindow::RefreshList); m_element_filter[i] = radio; if (i % 2 == 0) { radio_layout_left->addWidget(radio); } else { radio_layout_right->addWidget(radio); } } search_radio_layout->addLayout(radio_layout_left); search_radio_layout->setDirection(QBoxLayout::Direction::LeftToRight); search_radio_layout->addSpacing(35); search_radio_layout->addLayout(radio_layout_right); m_element_filter[0]->setText(tr("All")); m_element_filter[0]->setChecked(true); // i18n: One of the elements in the Skylanders games. Japanese: まほう. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Magic)]->setText(tr("Magic")); // i18n: One of the elements in the Skylanders games. Japanese: 水. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Water)]->setText(tr("Water")); // i18n: One of the elements in the Skylanders games. Japanese: マシン. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Tech)]->setText(tr("Tech")); // i18n: One of the elements in the Skylanders games. Japanese: 火. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Fire)]->setText(tr("Fire")); // i18n: One of the elements in the Skylanders games. Japanese: 土. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Earth)]->setText(tr("Earth")); // i18n: One of the elements in the Skylanders games. Japanese: ライフ. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Life)]->setText(tr("Life")); // i18n: One of the elements in the Skylanders games. Japanese: 風. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Air)]->setText(tr("Air")); // i18n: One of the elements in the Skylanders games. Japanese: アンデッド. For official // translations in other languages, check the SuperChargers manual at // https://support.activision.com/manuals m_element_filter[GetElementID(Element::Undead)]->setText(tr("Undead")); // i18n: One of the elements in the Skylanders games. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Dark)]->setText(tr("Dark")); // i18n: One of the elements in the Skylanders games. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_element_filter[GetElementID(Element::Light)]->setText(tr("Light")); m_element_filter[GetElementID(Element::Other)]->setText(tr("Other")); search_radio_group->setLayout(search_radio_layout); search_radio_scroll_area->setWidget(search_radio_group); search_element_layout->addWidget(search_radio_scroll_area); search_radio_scroll_area->setAlignment(Qt::AlignHCenter); search_element_group->setLayout(search_element_layout); search_element_group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); search_filters_layout->addWidget(search_element_group); // Widget: Filter by Type auto* search_type_group = new QGroupBox(tr("Figure type")); auto* search_type_layout = new QVBoxLayout(); auto* search_type_radio_scroll_area = new QScrollArea(); search_type_radio_scroll_area->setContentsMargins(0, 0, 0, 0); search_type_radio_scroll_area->setFrameStyle(QFrame::NoFrame); auto* search_type_radio_group = new QFrame(); search_type_radio_group->setContentsMargins(0, 0, 0, 0); auto* search_type_radio_layout = new QHBoxLayout(); auto* radio_type_layout_left = new QVBoxLayout(); auto* radio_type_layout_right = new QVBoxLayout(); for (int i = 0; i < NUM_SKYLANDER_TYPES; i++) { QRadioButton* radio = new QRadioButton(this); radio->setProperty("id", i); if (i == 0) { radio->setChecked(true); } connect(radio, &QRadioButton::toggled, this, &SkylanderPortalWindow::RefreshList); m_type_filter[i] = radio; if (i % 2 == 0) { radio_type_layout_left->addWidget(radio); } else { radio_type_layout_right->addWidget(radio); } } search_type_radio_layout->addLayout(radio_type_layout_left); search_type_radio_layout->addLayout(radio_type_layout_right); m_type_filter[0]->setText(tr("All")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Skylander)]->setText(tr("Skylander")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Giant)]->setText(tr("Giant")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Swapper)]->setText(tr("Swapper")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::TrapMaster)]->setText(tr("Trap Master")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Mini)]->setText(tr("Mini")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Item)]->setText(tr("Item")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Trophy)]->setText(tr("Trophy")); // i18n: One of the figure types in the Skylanders games. For official translations // in other languages, check the SuperChargers manual at https://support.activision.com/manuals m_type_filter[GetTypeID(Type::Vehicle)]->setText(tr("Vehicle")); // i18n: One of the figure types in the Skylanders games. m_type_filter[GetTypeID(Type::Trap)]->setText(tr("Trap")); search_type_radio_group->setLayout(search_type_radio_layout); search_type_radio_scroll_area->setWidget(search_type_radio_group); search_type_radio_scroll_area->setAlignment(Qt::AlignHCenter); search_type_layout->addWidget(search_type_radio_scroll_area); search_type_group->setLayout(search_type_layout); search_type_group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); search_filters_layout->addWidget(search_type_group); // Widget: Other Filters auto* other_box = new QGroupBox(tr("Other")); auto* other_layout = new QVBoxLayout; connect(m_only_show_collection, &QCheckBox::toggled, this, &SkylanderPortalWindow::RefreshList); other_layout->addWidget(m_only_show_collection); other_box->setLayout(other_layout); search_filters_layout->addWidget(other_box); search_checkbox_scroll_area->setMinimumWidth(search_radio_group->width() + 20); search_radio_scroll_area->setMinimumWidth(search_radio_group->width() + 20); other_box->setMinimumWidth(search_radio_group->width() + 20); search_filters_layout->addStretch(1); finder_list_layout->addLayout(search_filters_layout); finder_list_layout->addWidget(m_skylander_list); finder_layout->addLayout(finder_list_layout); main_layout->addLayout(finder_layout); return main_layout; } bool SkylanderPortalWindow::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) { // Close when escape is pressed if (static_cast(event)->matches(QKeySequence::Cancel)) hide(); } return false; } void SkylanderPortalWindow::closeEvent(QCloseEvent* event) { hide(); } // UI void SkylanderPortalWindow::EmulatePortal(bool emulate) { Config::SetBaseOrCurrent(Config::MAIN_EMULATE_SKYLANDER_PORTAL, emulate); m_group_skylanders->setVisible(emulate); m_command_buttons->setVisible(emulate); m_emulating = emulate; } void SkylanderPortalWindow::SelectCollectionPath() { QString dir = QDir::toNativeSeparators(DolphinFileDialog::getExistingDirectory( this, tr("Select Skylander Collection"), m_collection_path)); if (!dir.isEmpty()) { dir += QDir::separator(); m_path_edit->setText(dir); m_collection_path = dir; } Config::SetBase(Config::MAIN_SKYLANDERS_PATH, dir.toStdString()); if (m_only_show_collection->isChecked()) RefreshList(); } void SkylanderPortalWindow::LoadSelected() { if (!m_emulating) return; const u8 slot = GetCurrentSlot(); QString file_path; if (m_only_show_collection->isChecked()) { if (m_skylander_list->currentItem() == nullptr) return; file_path = m_collection_path + m_skylander_list->currentItem()->text() + QStringLiteral(".sky"); } else { file_path = GetFilePath(m_sky_id, m_sky_var); } if (file_path.isEmpty()) { QMessageBox::StandardButton create_file_response; create_file_response = QMessageBox::question(this, tr("Create Skylander File"), tr("Skylander not found in this collection. Create new file?"), QMessageBox::Yes | QMessageBox::No); if (create_file_response == QMessageBox::Yes) { QString predef_name = m_collection_path; const auto found_sky = IOS::HLE::USB::list_skylanders.find(std::make_pair(m_sky_id, m_sky_var)); if (found_sky != IOS::HLE::USB::list_skylanders.end()) { predef_name += QString::fromStdString(std::string(found_sky->second.name) + ".sky"); } else { // i18n: This is used to create a file name. The string must end in ".sky". const QString str = tr("Unknown(%1 %2).sky"); predef_name += str.arg(m_sky_id, m_sky_var); } CreateSkyfile(predef_name, true); } } else { m_last_skylander_path = QFileInfo(file_path).absolutePath() + QLatin1Char{'/'}; LoadSkyfilePath(slot, file_path); } } void SkylanderPortalWindow::LoadFromFile() { const u8 slot = GetCurrentSlot(); const QString file_path = DolphinFileDialog::getOpenFileName(this, tr("Select Skylander File"), m_last_skylander_path, tr("Skylander (*.sky *.bin *.dmp *.dump);;All Files (*)")); ; if (file_path.isEmpty()) { return; } m_last_skylander_path = QDir::toNativeSeparators(QFileInfo(file_path).absolutePath()) + QDir::separator(); LoadSkyfilePath(slot, file_path); } void SkylanderPortalWindow::CreateSkylanderAdvanced() { QDialog* create_window = new QDialog; auto* layout = new QVBoxLayout; auto* hbox_idvar = new QHBoxLayout(); auto* label_id = new QLabel(tr("ID:")); auto* label_var = new QLabel(tr("Variant:")); auto* edit_id = new QLineEdit(tr("0")); auto* edit_var = new QLineEdit(tr("0")); auto* rxv = new QRegularExpressionValidator(QRegularExpression(QStringLiteral("\\d*")), this); edit_id->setValidator(rxv); edit_var->setValidator(rxv); hbox_idvar->addWidget(label_id); hbox_idvar->addWidget(edit_id); hbox_idvar->addWidget(label_var); hbox_idvar->addWidget(edit_var); layout->addLayout(hbox_idvar); auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttons->button(QDialogButtonBox::Ok)->setText(tr("Create")); layout->addWidget(buttons); create_window->setLayout(layout); connect(buttons, &QDialogButtonBox::accepted, this, [=, this] { bool ok_id = false, ok_var = false; m_sky_id = edit_id->text().toUShort(&ok_id); if (!ok_id) { QMessageBox::warning(this, tr("Error converting value"), tr("ID entered is invalid!"), QMessageBox::Ok); return; } m_sky_var = edit_var->text().toUShort(&ok_var); if (!ok_var) { QMessageBox::warning(this, tr("Error converting value"), tr("Variant entered is invalid!"), QMessageBox::Ok); return; } QString predef_name = m_last_skylander_path; const auto found_sky = IOS::HLE::USB::list_skylanders.find(std::make_pair(m_sky_id, m_sky_var)); if (found_sky != IOS::HLE::USB::list_skylanders.end()) { predef_name += QString::fromStdString(std::string(found_sky->second.name) + ".sky"); } else { // i18n: This is used to create a file name. The string must end in ".sky". QString str = tr("Unknown(%1 %2).sky"); predef_name += str.arg(m_sky_id, m_sky_var); } QString file_path = DolphinFileDialog::getSaveFileName( this, tr("Create Skylander File"), predef_name, tr("Skylander (*.sky);;All Files (*)")); if (file_path.isEmpty()) { return; } CreateSkyfile(file_path, true); create_window->accept(); }); connect(buttons, &QDialogButtonBox::rejected, create_window, &QDialog::reject); create_window->show(); create_window->raise(); } void SkylanderPortalWindow::ModifySkylander() { if (auto sky_slot = m_sky_slots[GetCurrentSlot()]) { new SkylanderModifyDialog(this, sky_slot.value().portal_slot); } else { QMessageBox::warning(this, tr("Failed to modify Skylander!"), tr("Make sure there is a Skylander in slot %1!").arg(GetCurrentSlot()), QMessageBox::Ok); } } void SkylanderPortalWindow::ClearSlot(u8 slot) { auto& system = Core::System::GetInstance(); if (auto slot_infos = m_sky_slots[slot]) { if (!system.GetSkylanderPortal().RemoveSkylander(slot_infos->portal_slot)) { QMessageBox::warning(this, tr("Failed to clear Skylander!"), tr("Failed to clear the Skylander from slot %1!").arg(slot), QMessageBox::Ok); return; } m_sky_slots[slot].reset(); if (m_only_show_collection->isChecked()) RefreshList(); UpdateSlotNames(); } } // Behind the scenes void SkylanderPortalWindow::OnCollectionPathChanged() { m_collection_path = m_path_edit->text(); Config::SetBase(Config::MAIN_SKYLANDERS_PATH, m_path_edit->text().toStdString()); RefreshList(); } void SkylanderPortalWindow::OnEmulationStateChanged(Core::State state) { const bool running = state != Core::State::Uninitialized; m_enabled_checkbox->setEnabled(!running); } void SkylanderPortalWindow::UpdateCurrentIDs() { const u32 sky_info = m_skylander_list->currentItem()->data(1).toUInt(); if (sky_info != 0xFFFFFFFF) { m_sky_id = sky_info >> 16; m_sky_var = sky_info & 0xFFFF; } } void SkylanderPortalWindow::RefreshList() { const bool is_dark_theme = Settings::Instance().IsThemeDark(); const int row = m_skylander_list->currentRow(); m_skylander_list->clear(); if (m_only_show_collection->isChecked()) { const QDir collection = QDir(m_collection_path); auto& system = Core::System::GetInstance(); for (const auto& file : collection.entryInfoList( QStringList() << QStringLiteral("*.sky") << QStringLiteral("*.bin") << QStringLiteral("*.dmp") << QStringLiteral("*.dump"))) { File::IOFile sky_file(file.filePath().toStdString(), "r+b"); if (!sky_file) { continue; } std::array file_data; if (!sky_file.ReadBytes(file_data.data(), file_data.size())) { continue; } auto ids = system.GetSkylanderPortal().CalculateIDs(file_data); if (PassesFilter(file.baseName(), ids.first, ids.second)) { const uint qvar = (ids.first << 16) | ids.second; QListWidgetItem* skylander = new QListWidgetItem(file.baseName()); if (is_dark_theme) { skylander->setBackground(GetBaseColor(ids, true)); skylander->setForeground(QBrush(QColor(220, 220, 220))); } else { skylander->setBackground(GetBaseColor(ids, false)); skylander->setForeground(QBrush(QColor(0, 0, 0))); } skylander->setData(1, qvar); m_skylander_list->addItem(skylander); } } } else { for (const auto& entry : IOS::HLE::USB::list_skylanders) { int id = entry.first.first; int var = entry.first.second; if (PassesFilter(tr(entry.second.name), id, var)) { const uint qvar = (entry.first.first << 16) | entry.first.second; QListWidgetItem* skylander = new QListWidgetItem(tr(entry.second.name)); if (is_dark_theme) { skylander->setBackground(GetBaseColor(entry.first, true)); skylander->setForeground(QBrush(QColor(220, 220, 220))); } else { skylander->setBackground(GetBaseColor(entry.first, false)); skylander->setForeground(QBrush(QColor(0, 0, 0))); } skylander->setData(1, qvar); m_skylander_list->addItem(skylander); } } } if (m_skylander_list->count() >= row) { m_skylander_list->setCurrentItem(m_skylander_list->item(row), QItemSelectionModel::Select); } else if (m_skylander_list->count() > 0) { m_skylander_list->setCurrentItem(m_skylander_list->item(m_skylander_list->count() - 1), QItemSelectionModel::Select); } } void SkylanderPortalWindow::CreateSkyfile(const QString& path, bool load_after) { { IOS::HLE::USB::SkylanderFigure figure(path.toStdString()); if (!figure.Create(m_sky_id, m_sky_var)) { QMessageBox::warning( this, tr("Failed to create Skylander file!"), tr("Failed to create Skylander file:\n%1\n\nThe Skylander may already be on the portal.") .arg(path), QMessageBox::Ok); return; } figure.Close(); } m_last_skylander_path = QFileInfo(path).absolutePath() + QLatin1Char{'/'}; if (load_after) LoadSkyfilePath(GetCurrentSlot(), path); } void SkylanderPortalWindow::LoadSkyfilePath(u8 slot, const QString& path) { File::IOFile sky_file(path.toStdString(), "r+b"); if (!sky_file) { QMessageBox::warning(this, tr("Failed to open the Skylander file!"), tr("Failed to open the Skylander file:\n%1\n\nThe file may already be in " "use on the portal.") .arg(path), QMessageBox::Ok); return; } std::array file_data; if (!sky_file.ReadBytes(file_data.data(), file_data.size())) { QMessageBox::warning( this, tr("Failed to read the Skylander file!"), tr("Failed to read the Skylander file:\n%1\n\nThe file was too small.").arg(path), QMessageBox::Ok); return; } ClearSlot(slot); auto& system = Core::System::GetInstance(); const std::pair id_var = system.GetSkylanderPortal().CalculateIDs(file_data); const u8 portal_slot = system.GetSkylanderPortal().LoadSkylander( std::make_unique(std::move(sky_file))); if (portal_slot == 0xFF) { QMessageBox::warning(this, tr("Failed to load the Skylander file!"), tr("Failed to load the Skylander file:\n%1").arg(path), QMessageBox::Ok); return; } m_sky_slots[slot] = {portal_slot, id_var.first, id_var.second}; RefreshList(); UpdateSlotNames(); } void SkylanderPortalWindow::UpdateSlotNames() { for (auto i = 0; i < MAX_SKYLANDERS; i++) { QString display_string; if (auto sd = m_sky_slots[i]) { auto found_sky = IOS::HLE::USB::list_skylanders.find(std::make_pair(sd->m_sky_id, sd->m_sky_var)); if (found_sky != IOS::HLE::USB::list_skylanders.end()) { display_string = QString::fromStdString(found_sky->second.name); } else { // i18n: "Var" is short for "variant" display_string = tr("Unknown (Id:%1 Var:%2)").arg(sd->m_sky_id).arg(sd->m_sky_var); } } else { display_string = tr("None"); } m_edit_skylanders[i]->setText(display_string); } } // Helpers bool SkylanderPortalWindow::PassesFilter(const QString& name, u16 id, u16 var) const { const auto skypair = IOS::HLE::USB::list_skylanders.find(std::make_pair(id, var)); IOS::HLE::USB::SkyData character; if (skypair == IOS::HLE::USB::list_skylanders.end()) { return false; } else { character = skypair->second; } bool pass = false; // Check against active game filters for (size_t i = 0; i < NUM_SKYLANDER_GAMES; i++) { if (m_game_filters[i]->isChecked() && character.game == (Game)i) { pass = true; } } if (!pass) return false; // Check against search bar filter if (!name.contains(m_sky_search->text(), Qt::CaseInsensitive)) return false; // Check against active element filter if ((Element)GetElementRadio() != character.element && GetElementRadio() != 0) return false; // Check against active type filter if ((Type)GetTypeRadio() != character.type && GetTypeRadio() != 0) return false; return true; } QString SkylanderPortalWindow::GetFilePath(u16 id, u16 var) const { const QDir collection = QDir(m_collection_path); auto& system = Core::System::GetInstance(); for (const auto& file : collection.entryInfoList( QStringList() << QStringLiteral("*.sky") << QStringLiteral("*.bin") << QStringLiteral("*.dmp") << QStringLiteral("*.dump"))) { File::IOFile sky_file(file.filePath().toStdString(), "r+b"); if (!sky_file) { continue; } std::array file_data; if (!sky_file.ReadBytes(file_data.data(), file_data.size())) { continue; } auto ids = system.GetSkylanderPortal().CalculateIDs(file_data); if (ids.first == id && ids.second == var) { return file.filePath(); } } return {}; } u8 SkylanderPortalWindow::GetCurrentSlot() const { for (const auto* radio : m_slot_radios) { if (radio->isChecked()) { return radio->property("id").toInt(); } } return 0; } int SkylanderPortalWindow::GetElementRadio() const { for (const auto* radio : m_element_filter) { if (radio->isChecked()) { return radio->property("id").toInt(); } } return -1; } int SkylanderPortalWindow::GetTypeRadio() const { for (const auto* radio : m_type_filter) { if (radio->isChecked()) { return radio->property("id").toInt(); } } return -1; } QBrush SkylanderPortalWindow::GetBaseColor(std::pair ids, bool dark_theme) { auto skylander = IOS::HLE::USB::list_skylanders.find(ids); if (skylander == IOS::HLE::USB::list_skylanders.end()) return QBrush(dark_theme ? QColor(32, 32, 32) : QColor(255, 255, 255)); switch (skylander->second.game) { case Game::SpyrosAdv: return QBrush(dark_theme ? QColor(10, 42, 90) : QColor(240, 255, 240)); case Game::Giants: return QBrush(dark_theme ? QColor(120, 16, 12) : QColor(255, 240, 215)); case Game::SwapForce: return QBrush(dark_theme ? QColor(28, 45, 12) : QColor(240, 245, 255)); case Game::TrapTeam: return QBrush(dark_theme ? QColor(0, 56, 76) : QColor(255, 240, 240)); case Game::Superchargers: return QBrush(dark_theme ? QColor(90, 12, 12) : QColor(247, 228, 215)); default: return QBrush(dark_theme ? QColor(32, 32, 32) : QColor(255, 255, 255)); } } int SkylanderPortalWindow::GetGameID(Game game) { return (int)game; } int SkylanderPortalWindow::GetElementID(Element elem) { return (int)elem; } int SkylanderPortalWindow::GetTypeID(Type type) { return (int)type; }