diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 626605980..a22ae789c 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -996,40 +996,85 @@ void FullscreenUI::DoChangeDiscFromFile() void FullscreenUI::DoChangeDisc() { - if (!System::HasMediaSubImages()) + ImGuiFullscreen::ChoiceDialogOptions options; + + if (System::HasMediaSubImages()) { - DoChangeDiscFromFile(); + const u32 current_index = System::GetMediaSubImageIndex(); + const u32 count = System::GetMediaSubImageCount(); + options.reserve(count + 1); + options.emplace_back(FSUI_STR("From File..."), false); + + for (u32 i = 0; i < count; i++) + options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index); + + auto callback = [](s32 index, const std::string& title, bool checked) { + if (index == 0) + { + CloseChoiceDialog(); + DoChangeDiscFromFile(); + return; + } + else if (index > 0) + { + System::SwitchMediaSubImage(static_cast(index - 1)); + } + + QueueResetFocus(); + CloseChoiceDialog(); + ReturnToMainWindow(); + }; + + OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options), + std::move(callback)); + return; } - const u32 current_index = System::GetMediaSubImageIndex(); - const u32 count = System::GetMediaSubImageCount(); - ImGuiFullscreen::ChoiceDialogOptions options; - options.reserve(count + 1); - options.emplace_back("From File...", false); - - for (u32 i = 0; i < count; i++) - options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index); - - auto callback = [](s32 index, const std::string& title, bool checked) { - if (index == 0) + if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty()) + { + const auto lock = GameList::GetLock(); + const auto matches = GameList::GetMatchingEntriesForSerial(entry->disc_set_serials); + if (matches.size() > 1) { - CloseChoiceDialog(); - DoChangeDiscFromFile(); + options.reserve(matches.size() + 1); + options.emplace_back(FSUI_STR("From File..."), false); + + std::vector paths; + paths.reserve(matches.size()); + + const std::string& current_path = System::GetDiscPath(); + for (auto& [title, glentry] : matches) + { + options.emplace_back(std::move(title), current_path == glentry->path); + paths.push_back(glentry->path); + } + + auto callback = [paths = std::move(paths)](s32 index, const std::string& title, bool checked) { + if (index == 0) + { + CloseChoiceDialog(); + DoChangeDiscFromFile(); + return; + } + else if (index > 0) + { + System::InsertMedia(paths[index - 1].c_str()); + } + + QueueResetFocus(); + CloseChoiceDialog(); + ReturnToMainWindow(); + }; + + OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options), + std::move(callback)); + return; } - else if (index > 0) - { - System::SwitchMediaSubImage(static_cast(index - 1)); - } + } - QueueResetFocus(); - CloseChoiceDialog(); - ReturnToMainWindow(); - }; - - OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options), - std::move(callback)); + DoChangeDiscFromFile(); } void FullscreenUI::DoCheatsMenu() diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 013c505da..79f0818e8 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1033,6 +1033,50 @@ TinyString GameList::FormatTimespan(std::time_t timespan, bool long_format) return ret; } +std::vector> +GameList::GetMatchingEntriesForSerial(const gsl::span serials) +{ + std::vector> ret; + ret.reserve(serials.size()); + + for (const std::string& serial : serials) + { + const Entry* matching_entry = nullptr; + bool has_multiple_entries = false; + + for (const Entry& entry : s_entries) + { + if (entry.serial != serial) + continue; + + if (!matching_entry) + matching_entry = &entry; + else + has_multiple_entries = true; + } + + if (!matching_entry) + continue; + + if (!has_multiple_entries) + { + ret.emplace_back(matching_entry->title, matching_entry); + continue; + } + + // Have to add all matching files. + for (const Entry& entry : s_entries) + { + if (entry.serial != serial) + continue; + + ret.emplace_back(Path::GetFileName(entry.path), &entry); + } + } + + return ret; +} + bool GameList::DownloadCovers(const std::vector& url_templates, bool use_serial, ProgressCallback* progress, std::function save_callback) { diff --git a/src/core/game_list.h b/src/core/game_list.h index b0384b926..802e69fb5 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -10,6 +10,8 @@ #include "common/string.h" +#include "gsl/span" + #include #include #include @@ -102,6 +104,11 @@ std::string GetCoverImagePathForEntry(const Entry* entry); std::string GetCoverImagePath(const std::string& path, const std::string& serial, const std::string& title); std::string GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename, bool use_serial); +/// Returns a list of (title, entry) for entries matching serials. Titles will match the gamedb title, +/// except when two files have the same serial, in which case the filename will be used instead. +std::vector> +GetMatchingEntriesForSerial(const gsl::span serials); + /// Downloads covers using the specified URL templates. By default, covers are saved by title, but this can be changed /// with the use_serial parameter. save_callback optionall takes the entry and the path the new cover is saved to. bool DownloadCovers(const std::vector& url_templates, bool use_serial = false, diff --git a/src/core/system.cpp b/src/core/system.cpp index 086719f1c..4251b19ed 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -333,6 +333,11 @@ const std::string& System::GetGameTitle() return s_running_game_title; } +const GameDatabase::Entry* System::GetGameDatabaseEntry() +{ + return s_running_game_entry; +} + System::GameHash System::GetGameHash() { return s_running_game_hash; diff --git a/src/core/system.h b/src/core/system.h index fb71020f9..ba1833b3e 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -25,6 +25,11 @@ struct ImageInfo; struct Hash; } +namespace GameDatabase +{ +struct Entry; +} + struct SystemBootParameters { SystemBootParameters(); @@ -184,6 +189,7 @@ void FrameDone(); const std::string& GetDiscPath(); const std::string& GetGameSerial(); const std::string& GetGameTitle(); +const GameDatabase::Entry* GetGameDatabaseEntry(); GameHash GetGameHash(); bool IsRunningUnknownGame(); bool WasFastBooted(); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 3a55fb61e..78d4648d0 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -603,8 +603,9 @@ void MainWindow::onSystemDestroyed() void MainWindow::onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title) { - m_current_game_title = game_title.toStdString(); - m_current_game_serial = game_serial.toStdString(); + m_current_game_path = filename; + m_current_game_title = game_title; + m_current_game_serial = game_serial; updateWindowTitle(); // updateSaveStateMenus(path, serial, crc); @@ -916,18 +917,34 @@ void MainWindow::populateSaveStateMenu(const char* game_serial, QMenu* menu) void MainWindow::populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group) { - if (!s_system_valid || !System::HasMediaSubImages()) + if (!s_system_valid) return; - const u32 count = System::GetMediaSubImageCount(); - const u32 current = System::GetMediaSubImageIndex(); - for (u32 i = 0; i < count; i++) + if (System::HasMediaSubImages()) { - QAction* action = action_group->addAction(QString::fromStdString(System::GetMediaSubImageTitle(i))); - action->setCheckable(true); - action->setChecked(i == current); - connect(action, &QAction::triggered, [i]() { g_emu_thread->changeDiscFromPlaylist(i); }); - menu->addAction(action); + const u32 count = System::GetMediaSubImageCount(); + const u32 current = System::GetMediaSubImageIndex(); + for (u32 i = 0; i < count; i++) + { + QAction* action = action_group->addAction(QString::fromStdString(System::GetMediaSubImageTitle(i))); + action->setCheckable(true); + action->setChecked(i == current); + connect(action, &QAction::triggered, [i]() { g_emu_thread->changeDiscFromPlaylist(i); }); + menu->addAction(action); + } + } + else if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty()) + { + auto lock = GameList::GetLock(); + for (const auto& [title, glentry] : GameList::GetMatchingEntriesForSerial(entry->disc_set_serials)) + { + QAction* action = action_group->addAction(QString::fromStdString(title)); + QString path = QString::fromStdString(glentry->path); + action->setCheckable(true); + action->setChecked(path == m_current_game_path); + connect(action, &QAction::triggered, [path = std::move(path)]() { g_emu_thread->changeDisc(path); }); + menu->addAction(action); + } } } @@ -1155,12 +1172,12 @@ void MainWindow::onChangeDiscMenuAboutToHide() void MainWindow::onLoadStateMenuAboutToShow() { - populateLoadStateMenu(m_current_game_serial.c_str(), m_ui.menuLoadState); + populateLoadStateMenu(m_current_game_serial.toUtf8().constData(), m_ui.menuLoadState); } void MainWindow::onSaveStateMenuAboutToShow() { - populateSaveStateMenu(m_current_game_serial.c_str(), m_ui.menuSaveState); + populateSaveStateMenu(m_current_game_serial.toUtf8().constData(), m_ui.menuSaveState); } void MainWindow::onCheatsMenuAboutToShow() @@ -1706,9 +1723,9 @@ void MainWindow::updateWindowTitle() { QString suffix(QtHost::GetAppConfigSuffix()); QString main_title(QtHost::GetAppNameAndVersion() + suffix); - QString display_title(QString::fromStdString(m_current_game_title) + suffix); + QString display_title(m_current_game_title + suffix); - if (!s_system_valid || m_current_game_title.empty()) + if (!s_system_valid || m_current_game_title.isEmpty()) display_title = main_title; else if (isRenderingToMain()) main_title = display_title; @@ -2464,7 +2481,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav return true; // If we don't have a serial, we can't save state. - allow_save_to_state &= !m_current_game_serial.empty(); + allow_save_to_state &= !m_current_game_serial.isEmpty(); save_state &= allow_save_to_state; // Only confirm on UI thread because we need to display a msgbox. diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index c65efcaf3..a31c6fd1b 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -277,8 +277,9 @@ private: CheatManagerDialog* m_cheat_manager_dialog = nullptr; DebuggerWindow* m_debugger_window = nullptr; - std::string m_current_game_title; - std::string m_current_game_serial; + QString m_current_game_path; + QString m_current_game_title; + QString m_current_game_serial; bool m_was_paused_by_focus_loss = false; bool m_open_debugger_on_start = false;