diff --git a/src/frontend/main.cpp b/src/frontend/main.cpp index 0890baf..76e08a0 100644 --- a/src/frontend/main.cpp +++ b/src/frontend/main.cpp @@ -2,11 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include #include #include +#include #include #include "common/file_util.h" #include "frontend/import_dialog.h" @@ -36,18 +38,11 @@ bool IsConfigGood(const Core::Config& config) { MainDialog::MainDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique()) { ui->setupUi(this); - setFixedWidth(width()); + ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)->setText(tr("Refresh")); LoadPresetConfig(); - connect(ui->advancedButton, &QPushButton::clicked, [this] { - if (ui->customGroupBox->isVisible()) { - HideAdvanced(); - } else { - ShowAdvanced(); - } - }); connect(ui->utilitiesButton, &QPushButton::clicked, [this] { UtilitiesDialog dialog(this); dialog.exec(); @@ -61,34 +56,19 @@ MainDialog::MainDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique, 9> fields{{ - {ui->sdmcPath, ui->sdmcPathExplore, 0}, - {ui->userPath, ui->userPathExplore, 0}, - {ui->movableSedPath, ui->movableSedExplore, 1}, - {ui->bootrom9Path, ui->bootrom9Explore, 1}, - {ui->safeModeFirmPath, ui->safeModeFirmExplore, 0}, - {ui->seeddbPath, ui->seeddbExplore, 1}, - {ui->secretSectorPath, ui->secretSectorExplore, 1}, - {ui->configSavegamePath, ui->configSavegameExplore, 1}, - {ui->systemArchivesPath, ui->systemArchivesExplore, 0}, - }}; + connect(ui->main, &QTreeWidget::itemSelectionChanged, [this] { + ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok) + ->setEnabled(!ui->main->selectedItems().empty()); + }); + connect(ui->main, &QTreeWidget::itemDoubleClicked, [this] { + if (!ui->main->selectedItems().empty()) + LaunchImportDialog(); + }); - // TODO: better handling (filter) - for (auto [field, button, isFile] : fields) { - connect(button, &QToolButton::clicked, [this, field, isFile] { - QString path; - if (isFile) { - path = QFileDialog::getOpenFileName(this, tr("Select File")); - } else { - path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); - } - - if (!path.isEmpty()) { - field->setText(path); - } - }); - } + ui->main->setIndentation(4); + ui->main->setColumnWidth(0, 0.3 * width()); + ui->main->setColumnWidth(1, 0.4 * width()); + ui->main->setColumnWidth(2, 0.2 * width()); // Set up device watcher auto* device_watcher = new QDeviceWatcher(this); @@ -100,8 +80,10 @@ MainDialog::MainDialog(QWidget* parent) : QDialog(parent), ui(std::make_uniqueconfigSelect->clear(); + ui->main->clear(); preset_config_list.clear(); for (const auto& storage : QStorageInfo::mountedVolumes()) { @@ -111,103 +93,95 @@ void MainDialog::LoadPresetConfig() { auto list = Core::LoadPresetConfig(storage.rootPath().toStdString()); for (std::size_t i = 0; i < list.size(); ++i) { + if (!IsConfigGood(list[i])) { + return; + } preset_config_list.emplace_back(list[i]); - ui->configSelect->addItem(QString::fromStdString(list[i].sdmc_path)); + + QString path = storage.rootPath(); + if (path.endsWith(QLatin1Char{'/'}) || path.endsWith(QLatin1Char{'\\'})) { + path.remove(path.size() - 1, 1); + } + + // Get ID0 + QString id0 = tr("Unknown"); + std::smatch match; + if (std::regex_match(list[i].sdmc_path, match, sdmc_path_regex)) { + if (match.size() >= 5) { + id0 = QString::fromStdString(match[3].str()); + } + } + + // Get status + QString status = tr("Good"); + if (list[i].safe_mode_firm_path.empty() || list[i].config_savegame_path.empty() || + list[i].system_archives_path.empty()) { + + status = tr("Missing System Files"); + } else if (list[i].seed_db_path.empty()) { + status = tr("Good, Missing Seeds"); + } + + auto* item = new QTreeWidgetItem{{path, id0, status}}; + ui->main->invisibleRootItem()->addChild(item); } } - if (preset_config_list.empty()) { - // Clear the text - ui->sdmcPath->setText(QString{}); - ui->userPath->setText( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))); - ui->movableSedPath->setText(QString{}); - ui->bootrom9Path->setText(QString{}); - ui->safeModeFirmPath->setText(QString{}); - ui->seeddbPath->setText(QString{}); - ui->secretSectorPath->setText(QString{}); - ui->configSavegamePath->setText(QString{}); - ui->systemArchivesPath->setText(QString{}); - - ui->advancedButton->setVisible(false); - ShowAdvanced(); - ui->configSelect->addItem(tr("None")); - ui->configSelect->setCurrentText(tr("None")); - } else { - ui->advancedButton->setVisible(true); - if (ui->customGroupBox->isVisible()) { - HideAdvanced(); - } - } -} - -void MainDialog::ShowAdvanced() { - ui->configSelect->setEnabled(false); - ui->advancedButton->setText(tr("Hide Custom Config")); - ui->customGroupBox->setVisible(true); - - setMaximumHeight(1000000); - adjustSize(); - - const int index = ui->configSelect->currentIndex(); - ui->configSelect->addItem(tr("Custom")); - ui->configSelect->setCurrentText(tr("Custom")); - - if (index == -1) { - return; - } - - // Load preset data - const auto config = preset_config_list[static_cast(index)]; - ui->sdmcPath->setText(QString::fromStdString(config.sdmc_path)); - ui->userPath->setText(QString::fromStdString(config.user_path)); - ui->movableSedPath->setText(QString::fromStdString(config.movable_sed_path)); - ui->bootrom9Path->setText(QString::fromStdString(config.bootrom_path)); - ui->safeModeFirmPath->setText(QString::fromStdString(config.safe_mode_firm_path)); - ui->seeddbPath->setText(QString::fromStdString(config.seed_db_path)); - ui->secretSectorPath->setText(QString::fromStdString(config.secret_sector_path)); - ui->configSavegamePath->setText(QString::fromStdString(config.config_savegame_path)); - ui->systemArchivesPath->setText(QString::fromStdString(config.system_archives_path)); -} - -void MainDialog::HideAdvanced() { - ui->configSelect->setEnabled(true); - ui->advancedButton->setText(tr("Customize...")); - ui->customGroupBox->setVisible(false); - - LoadPresetConfig(); - - setMaximumHeight(130); - adjustSize(); -} - -Core::Config MainDialog::GetCurrentConfig() { - if (ui->customGroupBox->isVisible()) { - Core::Config config{ - /*sdmc_path*/ ui->sdmcPath->text().toStdString(), - /*user_path*/ ui->userPath->text().toStdString(), - /*movable_sed_path*/ ui->movableSedPath->text().toStdString(), - /*bootrom_path*/ ui->bootrom9Path->text().toStdString(), - /*safe_mode_firm_path*/ ui->safeModeFirmPath->text().toStdString(), - /*seed_db_path*/ ui->seeddbPath->text().toStdString(), - /*secret_sector_path*/ ui->secretSectorPath->text().toStdString(), - /*config_savegame_path*/ ui->configSavegamePath->text().toStdString(), - /*system_archives_path*/ ui->systemArchivesPath->text().toStdString(), - }; - return config; - } else { - return preset_config_list[ui->configSelect->currentIndex()]; - } + auto* item = new QTreeWidgetItem{{tr("Browse SD Card Root...")}}; + item->setFirstColumnSpanned(true); + ui->main->invisibleRootItem()->addChild(item); } void MainDialog::LaunchImportDialog() { - const auto& config = GetCurrentConfig(); - if (!IsConfigGood(config)) { - QMessageBox::critical(this, tr("Incomplete Config"), - tr("Your config is missing some of the required fields.")); + auto* item = ui->main->currentItem(); + if (!item) { return; } + Core::Config config; + const auto index = ui->main->invisibleRootItem()->indexOfChild(item); + if (index == ui->main->invisibleRootItem()->childCount() - 1) { + const QString path = QFileDialog::getExistingDirectory(this, tr("Select SD Card Root")); + if (path.isEmpty()) { + return; + } + + const auto& list = Core::LoadPresetConfig(path.toStdString()); + if (list.size() > 1) { + QMessageBox::information( + this, tr("threeSD"), + tr("You have more than one 3DS data on your SD Card.\nthreeSD will " + "select the first one for you.")); + } else if (list.empty() || !IsConfigGood(list[0])) { + QMessageBox::critical( + this, tr("Error"), + tr("Could not load configuration.
Please check if you have followed the " + "guide correctly.")); + return; + } + + config = list[0]; + } else { + config = preset_config_list.at(index); + } + + // Check config integrity + if (config.safe_mode_firm_path.empty() || config.config_savegame_path.empty() || + config.system_archives_path.empty()) { + QMessageBox::warning( + this, tr("Warning"), + tr("Certain system files are missing from your configuration.
Some contents " + "may not be importable, or may not run.
Please check if you have followed the guide " + "correctly.")); + } else if (config.seed_db_path.empty()) { + QMessageBox::warning(this, tr("Warning"), + tr("Seed database is missing from your configuration.
Your system " + "likely does not have any seeds.
However, if it does have any, " + "imported games using seed encryption may not work.")); + } + ImportDialog dialog(this, config); dialog.exec(); } diff --git a/src/frontend/main.h b/src/frontend/main.h index 9a9dab2..3e8bb0d 100644 --- a/src/frontend/main.h +++ b/src/frontend/main.h @@ -21,9 +21,6 @@ public: private: void LoadPresetConfig(); - void ShowAdvanced(); - void HideAdvanced(); - Core::Config GetCurrentConfig(); void LaunchImportDialog(); std::vector preset_config_list; diff --git a/src/frontend/main.ui b/src/frontend/main.ui index 9d8fc63..bdfe4a8 100644 --- a/src/frontend/main.ui +++ b/src/frontend/main.ui @@ -7,7 +7,7 @@ 0 0 800 - 130 + 300 @@ -19,28 +19,7 @@ - Auto-detected Configuration: - - - - - - - - 0 - 0 - - - - - - - - - - - - Customize... + Select your SD card root, or manually browse when not detected. @@ -61,195 +40,22 @@ - - - Custom - - - false - - - - - - SDMC (Required) - - - Path to the Nintendo 3DS/<ID0>/<ID1> folder. - - - - - - - Path to the Nintendo 3DS/<ID0>/<ID1> folder. - - - - - - - ... - - - - - - - Citra User Directory (Required) - - - Path to the User Directory of Citra to put imported content into. - - - - - - - Path to the User Directory of Citra to put imported content into. - - - - - - - ... - - - - - - - movable.sed (Required) - - - - - - - - - - - 0 - 0 - - - - ... - - - - - - - boot9.bin (Required) - - - - - - - - - - ... - - - - - - - Safe mode firm - - - Path to the safe mode firm.<br>This is a folder, and needs to contain another folder named 'new' or 'old' corresponding to the system's type.<br>The 'new' or 'old' folder should hold files from the 'content' folder of the firm title in NAND. (i.e. tmd and app) - - - - - - - Path to the safe mode firm.<br>This is a folder, and needs to contain another folder named 'new' or 'old' corresponding to the system's type.<br>The 'new' or 'old' folder should hold files from the 'content' folder of the firm title in NAND. (i.e. tmd and app) - - - - - - - ... - - - - - - - seeddb.bin - - - - - - - - - - ... - - - - - - - sector0x96.bin - - - - - - - - - - ... - - - - - - - Config savegame - - - - - - - - - - ... - - - - - - - System archives - - - - - - - - - - ... - - - - + + + + Path + + + + + ID + + + + + Status + +