diff --git a/src/launcher/forms/configuration_list_widget.ui b/src/launcher/forms/configuration_list_widget.ui
index 54f1fac53..256a185e2 100644
--- a/src/launcher/forms/configuration_list_widget.ui
+++ b/src/launcher/forms/configuration_list_widget.ui
@@ -119,36 +119,64 @@
- -
-
-
-
- 36
- 36
-
-
-
- <html><head/><body><p>Delete configuration</p></body></html>
-
-
-
-
-
-
-
-
+
-
+
+
- 32
- 32
-
-
-
- true
-
-
-
- -
-
+ 36
+ 36
+
+
+
+ <html><head/><body><p>Delete configuration</p></body></html>
+
+
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 96
+ 36
+
+
+
+ Import game folder (containing eboot.bin)
+
+
+ Import
+
+
+
+ 20
+ 20
+
+
+
+ Qt::ToolButtonTextBesideIcon
+
+
+ false
+
+
+
+ -
+
Qt::Horizontal
diff --git a/src/launcher/include/configurationListWidget.h b/src/launcher/include/configurationListWidget.h
index ca0061170..ca7d29e52 100644
--- a/src/launcher/include/configurationListWidget.h
+++ b/src/launcher/include/configurationListWidget.h
@@ -63,6 +63,7 @@ protected slots:
void edit_global_settings();
void edit_input_mapping();
void run_configuration();
+ void import_game();
void list_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
void list_itemDoubleClicked(QTreeWidgetItem* witem, int column);
void show_context_menu(const QPoint& pos);
diff --git a/src/launcher/src/configurationListWidget.cpp b/src/launcher/src/configurationListWidget.cpp
index aeb4aa61e..8d468df23 100644
--- a/src/launcher/src/configurationListWidget.cpp
+++ b/src/launcher/src/configurationListWidget.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -234,6 +235,11 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
m_ui->edit_button->setIcon(QIcon(QStringLiteral(":/icons/edit-configuration.svg")));
m_ui->delete_button->setIcon(QIcon(QStringLiteral(":/icons/remove-configuration.svg")));
+ m_ui->import_button->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon));
+ m_ui->import_button->setStyleSheet(QStringLiteral(
+ "QToolButton { background: rgba(32,118,210,90); border: 1px solid rgba(80,160,255,120); "
+ "border-radius: 5px; padding: 3px 8px; color: #eef4ff; }"
+ "QToolButton:hover { background: rgba(32,118,210,140); border-color: rgba(80,160,255,180); }"));
m_ui->delete_button->setEnabled(false);
m_ui->edit_button->setEnabled(false);
@@ -259,6 +265,8 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
&ConfigurationListWidget::edit_configuration);
connect(m_ui->delete_button, &QToolButton::clicked, this,
&ConfigurationListWidget::delete_configuartion);
+ connect(m_ui->import_button, &QToolButton::clicked, this,
+ &ConfigurationListWidget::import_game);
connect(m_ui->cfgs_list, &QTreeWidget::currentItemChanged, this,
&ConfigurationListWidget::list_currentItemChanged);
connect(m_ui->cfgs_list, &QTreeWidget::itemDoubleClicked, this,
@@ -731,6 +739,80 @@ void ConfigurationListWidget::edit_input_mapping() {
}
}
+void ConfigurationListWidget::import_game() {
+ const QString selected = QFileDialog::getExistingDirectory(
+ this, tr("Select game folder (containing eboot.bin) or folder containing games"),
+ QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
+
+ if (selected.isEmpty()) {
+ return;
+ }
+
+ QDir dir(selected);
+ QString dir_to_add;
+ QString expected_game_path;
+
+ if (dir.exists(QStringLiteral("eboot.bin"))) {
+ // Selected a single game folder — add its parent so Scan finds it
+ // (Scan only searches subdirectories of game_dirs, not the dirs themselves)
+ dir_to_add = QDir::cleanPath(dir.absolutePath() + QStringLiteral("/.."));
+ expected_game_path = QDir::cleanPath(dir.absolutePath());
+ } else {
+ dir_to_add = QDir::cleanPath(dir.absolutePath());
+ }
+
+ const QString normalized = NormalizeGameDirectory(dir_to_add);
+ if (normalized.isEmpty()) {
+ return;
+ }
+
+ const QString key = PathKey(normalized);
+ bool already_exists = false;
+ for (const auto& existing: m_game_dirs) {
+ if (PathKey(existing) == key) {
+ already_exists = true;
+ break;
+ }
+ }
+
+ if (!already_exists) {
+ m_game_dirs.append(normalized);
+ m_game_dirs = NormalizeGameDirectories(m_game_dirs);
+ WriteSettings();
+ }
+
+ const int before_count = m_ui->cfgs_list->topLevelItemCount();
+ ScanGameDirectory();
+
+ if (!expected_game_path.isEmpty()) {
+ const QString expected_key = PathKey(expected_game_path);
+ for (int i = 0; i < m_ui->cfgs_list->topLevelItemCount(); ++i) {
+ auto* item = static_cast(m_ui->cfgs_list->topLevelItem(i));
+ if (PathKey(item->GetInfo().game_path) == expected_key) {
+ m_ui->cfgs_list->setCurrentItem(item);
+ SelectItem(item);
+ break;
+ }
+ }
+ }
+
+ if (m_ui->cfgs_list->topLevelItemCount() == 0) {
+ QMessageBox::information(this, tr("Import game"),
+ tr("No games found in the selected folder.\n\n"
+ "Make sure the folder contains a game with eboot.bin "
+ "or subfolders with games."));
+ } else if (!expected_game_path.isEmpty() && m_ui->cfgs_list->topLevelItemCount() == before_count) {
+ // Single game import didn't appear
+ const int after_count = m_ui->cfgs_list->topLevelItemCount();
+ if (after_count == before_count) {
+ QMessageBox::warning(this, tr("Import game"),
+ tr("The selected game folder was added, but no game was detected.\n\n"
+ "Expected eboot.bin at: %1")
+ .arg(expected_game_path));
+ }
+ }
+}
+
void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) {
delete m_custom_infos.take(item->GetInfo().game_path);
}