diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 618b644b38..c429b7ee2c 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -54,6 +54,12 @@ #include "DiscIO/Enums.h" #include "DiscIO/Volume.h" +std::vector ReadM3UFile(const std::string& path) +{ + // TODO + return {}; +} + BootParameters::BootParameters(Parameters&& parameters_, const std::optional& savestate_path_) : parameters(std::move(parameters_)), savestate_path(savestate_path_) @@ -61,40 +67,68 @@ BootParameters::BootParameters(Parameters&& parameters_, } std::unique_ptr -BootParameters::GenerateFromFile(const std::string& path, +BootParameters::GenerateFromFile(std::string boot_path, const std::optional& savestate_path) { - const bool is_drive = Common::IsCDROMDevice(path); + return GenerateFromFile(std::vector{std::move(boot_path)}, savestate_path); +} + +std::unique_ptr +BootParameters::GenerateFromFile(std::vector paths, + const std::optional& savestate_path) +{ + ASSERT(!paths.empty()); + + const bool is_drive = Common::IsCDROMDevice(paths.front()); // Check if the file exist, we may have gotten it from a --elf command line // that gave an incorrect file name - if (!is_drive && !File::Exists(path)) + if (!is_drive && !File::Exists(paths.front())) { - PanicAlertT("The specified file \"%s\" does not exist", path.c_str()); + PanicAlertT("The specified file \"%s\" does not exist", paths.front().c_str()); return {}; } std::string extension; - SplitPath(path, nullptr, nullptr, &extension); + SplitPath(paths.front(), nullptr, nullptr, &extension); std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + if (extension == ".m3u") + { + std::vector new_paths = ReadM3UFile(paths.front()); + if (!new_paths.empty()) + { + paths = new_paths; + + SplitPath(paths.front(), nullptr, nullptr, &extension); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + } + } + + const std::string path = paths.front(); + if (paths.size() == 1) + paths.clear(); + static const std::unordered_set disc_image_extensions = { {".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".dol", ".elf"}}; if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive) { std::unique_ptr volume = DiscIO::CreateVolumeFromFilename(path); if (volume) - return std::make_unique(Disc{path, std::move(volume)}, savestate_path); + { + return std::make_unique(Disc{std::move(path), std::move(volume), paths}, + savestate_path); + } if (extension == ".elf") { - return std::make_unique(Executable{path, std::make_unique(path)}, - savestate_path); + return std::make_unique( + Executable{std::move(path), std::make_unique(path)}, savestate_path); } if (extension == ".dol") { - return std::make_unique(Executable{path, std::make_unique(path)}, - savestate_path); + return std::make_unique( + Executable{std::move(path), std::make_unique(path)}, savestate_path); } if (is_drive) @@ -113,10 +147,10 @@ BootParameters::GenerateFromFile(const std::string& path, } if (extension == ".dff") - return std::make_unique(DFF{path}, savestate_path); + return std::make_unique(DFF{std::move(path)}, savestate_path); if (extension == ".wad") - return std::make_unique(DiscIO::WiiWAD{path}, savestate_path); + return std::make_unique(DiscIO::WiiWAD{std::move(path)}, savestate_path); PanicAlertT("Could not recognize file %s", path.c_str()); return {}; @@ -136,10 +170,11 @@ BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_) // Inserts a disc into the emulated disc drive and returns a pointer to it. // The returned pointer must only be used while we are still booting, // because DVDThread can do whatever it wants to the disc after that. -static const DiscIO::Volume* SetDisc(std::unique_ptr volume) +static const DiscIO::Volume* SetDisc(std::unique_ptr volume, + std::vector auto_disc_change_paths = {}) { const DiscIO::Volume* pointer = volume.get(); - DVDInterface::SetDisc(std::move(volume)); + DVDInterface::SetDisc(std::move(volume), auto_disc_change_paths); return pointer; } @@ -326,7 +361,7 @@ bool CBoot::BootUp(std::unique_ptr boot) bool operator()(BootParameters::Disc& disc) const { NOTICE_LOG(BOOT, "Booting from disc: %s", disc.path.c_str()); - const DiscIO::Volume* volume = SetDisc(std::move(disc.volume)); + const DiscIO::Volume* volume = SetDisc(std::move(disc.volume), disc.auto_disc_change_paths); if (!volume) return false; @@ -420,7 +455,7 @@ bool CBoot::BootUp(std::unique_ptr boot) if (ipl.disc) { NOTICE_LOG(BOOT, "Inserting disc: %s", ipl.disc->path.c_str()); - SetDisc(DiscIO::CreateVolumeFromFilename(ipl.disc->path)); + SetDisc(DiscIO::CreateVolumeFromFilename(ipl.disc->path), ipl.disc->auto_disc_change_paths); } if (LoadMapFromFilename()) diff --git a/Source/Core/Core/Boot/Boot.h b/Source/Core/Core/Boot/Boot.h index 589b9b2056..7cb7b39989 100644 --- a/Source/Core/Core/Boot/Boot.h +++ b/Source/Core/Core/Boot/Boot.h @@ -40,6 +40,7 @@ struct BootParameters { std::string path; std::unique_ptr volume; + std::vector auto_disc_change_paths; }; struct Executable @@ -69,7 +70,9 @@ struct BootParameters }; static std::unique_ptr - GenerateFromFile(const std::string& boot_path, + GenerateFromFile(std::string boot_path, const std::optional& savestate_path = {}); + static std::unique_ptr + GenerateFromFile(std::vector paths, const std::optional& savestate_path = {}); using Parameters = std::variant; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 155c8d4933..6dc1c45eb3 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -104,6 +104,7 @@ const ConfigInfo MAIN_CUSTOM_RTC_VALUE{{System::Main, "Core", "CustomRTCVal const ConfigInfo MAIN_ENABLE_SIGNATURE_CHECKS{{System::Main, "Core", "EnableSignatureChecks"}, true}; const ConfigInfo MAIN_REDUCE_POLLING_RATE{{System::Main, "Core", "ReducePollingRate"}, false}; +const ConfigInfo MAIN_AUTO_DISC_CHANGE{{System::Main, "Core", "AutoDiscChange"}, false}; // Main.DSP diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 65f4662bc7..d932416eb7 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -78,6 +78,7 @@ extern const ConfigInfo MAIN_CUSTOM_RTC_ENABLE; extern const ConfigInfo MAIN_CUSTOM_RTC_VALUE; extern const ConfigInfo MAIN_ENABLE_SIGNATURE_CHECKS; extern const ConfigInfo MAIN_REDUCE_POLLING_RATE; +extern const ConfigInfo MAIN_AUTO_DISC_CHANGE; // Main.DSP diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index be51266c2f..146fdd1120 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -31,6 +31,7 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location) Config::MAIN_DEFAULT_ISO.location, Config::MAIN_MEMCARD_A_PATH.location, Config::MAIN_MEMCARD_B_PATH.location, + Config::MAIN_AUTO_DISC_CHANGE.location, // Graphics.Hardware diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 2dda449bad..b335be618c 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -16,8 +16,10 @@ #include "Common/Align.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Common/Config/Config.h" #include "Common/Logging/Log.h" +#include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/CoreTiming.h" #include "Core/HW/AudioInterface.h" @@ -36,6 +38,8 @@ #include "DiscIO/Volume.h" #include "DiscIO/VolumeWii.h" +#include "VideoCommon/OnScreenDisplay.h" + // The minimum time it takes for the DVD drive to process a command (in // microseconds) constexpr u64 COMMAND_LATENCY_US = 300; @@ -231,6 +235,8 @@ static u64 s_read_buffer_end_offset; // Disc changing static std::string s_disc_path_to_insert; +static std::vector s_auto_disc_change_paths; +static size_t s_auto_disc_change_index; // Events static CoreTiming::EventType* s_finish_executing_command; @@ -441,11 +447,21 @@ void Shutdown() DVDThread::Stop(); } -void SetDisc(std::unique_ptr disc) +void SetDisc(std::unique_ptr disc, + std::optional> auto_disc_change_paths = {}) { if (disc) s_current_partition = disc->GetGamePartition(); + if (auto_disc_change_paths) + { + ASSERT_MSG(DISCIO, (*auto_disc_change_paths).size() != 1, + "Cannot automatically change between one disc"); + + s_auto_disc_change_paths = *auto_disc_change_paths; + s_auto_disc_change_index = 0; + } + DVDThread::SetDisc(std::move(disc)); SetLidOpen(); } @@ -457,7 +473,7 @@ bool IsDiscInside() static void EjectDiscCallback(u64 userdata, s64 cyclesLate) { - SetDisc(nullptr); + SetDisc(nullptr, {}); } static void InsertDiscCallback(u64 userdata, s64 cyclesLate) @@ -466,7 +482,7 @@ static void InsertDiscCallback(u64 userdata, s64 cyclesLate) DiscIO::CreateVolumeFromFilename(s_disc_path_to_insert); if (new_volume) - SetDisc(std::move(new_volume)); + SetDisc(std::move(new_volume), {}); else PanicAlertT("The disc that was about to be inserted couldn't be found."); @@ -479,6 +495,20 @@ void EjectDisc() CoreTiming::ScheduleEvent(0, s_eject_disc); } +// Must only be called on the CPU thread +void ChangeDisc(const std::vector& paths) +{ + ASSERT_MSG(DISCIO, !paths.empty(), "Trying to insert an empty list of discs"); + + if (paths.size() > 1) + { + s_auto_disc_change_paths = paths; + s_auto_disc_change_index = 0; + } + + ChangeDisc(paths[0]); +} + // Must only be called on the CPU thread void ChangeDisc(const std::string& new_path) { @@ -493,6 +523,28 @@ void ChangeDisc(const std::string& new_path) s_disc_path_to_insert = new_path; CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), s_insert_disc); Movie::SignalDiscChange(new_path); + + for (size_t i = 0; i < s_auto_disc_change_paths.size(); ++i) + { + if (s_auto_disc_change_paths[i] == new_path) + { + s_auto_disc_change_index = i; + return; + } + } + + s_auto_disc_change_paths.clear(); +} + +// Must only be called on the CPU thread +bool AutoChangeDisc() +{ + if (s_auto_disc_change_paths.empty()) + return false; + + s_auto_disc_change_index = (s_auto_disc_change_index + 1) % s_auto_disc_change_paths.size(); + ChangeDisc(s_auto_disc_change_paths[s_auto_disc_change_index]); + return true; } void SetLidOpen() @@ -983,12 +1035,20 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr break; case DVDLowStopMotor: + { INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", command_1 ? "eject" : "", command_2 ? "kill!" : ""); - if (command_1 && !command_2) + bool auto_disc_change = Config::Get(Config::MAIN_AUTO_DISC_CHANGE) && !Movie::IsPlayingInput(); + if (auto_disc_change) + auto_disc_change = AutoChangeDisc(); + if (auto_disc_change) + OSD::AddMessage("Changing discs automatically...", OSD::Duration::NORMAL); + + if (!auto_disc_change && command_1 && !command_2) EjectDiscCallback(0, 0); break; + } // DVD Audio Enable/Disable (Immediate). GC uses this, and apparently Wii also does...? case DVDLowAudioBufferConfig: diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index fc23fb8bbd..e345dd07a6 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -111,10 +111,13 @@ void DoState(PointerWrap& p); void RegisterMMIO(MMIO::Mapping* mmio, u32 base); -void SetDisc(std::unique_ptr disc); +void SetDisc(std::unique_ptr disc, + std::optional> auto_disc_change_paths); bool IsDiscInside(); -void EjectDisc(); // Must only be called on the CPU thread -void ChangeDisc(const std::string& new_path); // Must only be called on the CPU thread +void EjectDisc(); // Must only be called on the CPU thread +void ChangeDisc(const std::vector& paths); // Must only be called on the CPU thread +void ChangeDisc(const std::string& new_path); // Must only be called on the CPU thread +bool AutoChangeDisc(); // Must only be called on the CPU thread // This function returns true and calls SConfig::SetRunningGameMetadata(Volume&, Partition&) // if both of the following conditions are true: diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index d779f0a71d..ae12021e47 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -1176,29 +1176,13 @@ void PlayController(GCPadStatus* PadStatus, int controllerID) PadStatus->button |= PAD_TRIGGER_R; if (s_padState.disc) { - // This implementation assumes the disc change will only happen once. Trying - // to change more than that will cause it to load the last disc every time. - // As far as I know, there are no 3+ disc games, so this should be fine. - bool found = false; - std::string path; - for (const std::string& iso_folder : SConfig::GetInstance().m_ISOFolder) - { - path = iso_folder + '/' + s_discChange; - if (File::Exists(path)) + Core::RunAsCPUThread([] { + if (!DVDInterface::AutoChangeDisc()) { - found = true; - break; + CPU::Break(); + PanicAlertT("Change the disc to %s", s_discChange.c_str()); } - } - if (found) - { - Core::RunAsCPUThread([&path] { DVDInterface::ChangeDisc(path); }); - } - else - { - CPU::Break(); - PanicAlertT("Change the disc to %s", s_discChange.c_str()); - } + }); } if (s_padState.reset) diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 6cd71bd353..697e4cbe6d 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -368,7 +368,10 @@ int main(int argc, char* argv[]) std::unique_ptr boot; if (options.is_set("exec")) { - boot = BootParameters::GenerateFromFile(static_cast(options.get("exec"))); + const std::list paths_list = options.all("exec"); + const std::vector paths{std::make_move_iterator(std::begin(paths_list)), + std::make_move_iterator(std::end(paths_list))}; + boot = BootParameters::GenerateFromFile(paths); } else if (options.is_set("nand_title")) { diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index c2a098281f..0af57f92ba 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -737,6 +737,17 @@ bool GameList::HasMultipleSelected() const m_grid->selectionModel()->selectedIndexes().size() > 1; } +std::shared_ptr GameList::FindGame(const std::string& path) const +{ + return m_model->FindGame(path); +} + +std::shared_ptr +GameList::FindSecondDisc(const UICommon::GameFile& game) const +{ + return m_model->FindSecondDisc(game); +} + void GameList::SetViewColumn(int col, bool view) { m_list->setColumnHidden(col, !view); diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index fac9932c3c..e7782651be 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -30,6 +30,8 @@ public: std::shared_ptr GetSelectedGame() const; QList> GetSelectedGames() const; bool HasMultipleSelected() const; + std::shared_ptr FindGame(const std::string& path) const; + std::shared_ptr FindSecondDisc(const UICommon::GameFile& game) const; void SetListView() { SetPreferredView(true); } void SetGridView() { SetPreferredView(false); } diff --git a/Source/Core/DolphinQt/GameList/GameListModel.cpp b/Source/Core/DolphinQt/GameList/GameListModel.cpp index 43bc61f9a1..e7440ce1e2 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.cpp +++ b/Source/Core/DolphinQt/GameList/GameListModel.cpp @@ -278,7 +278,7 @@ void GameListModel::AddGame(const std::shared_ptr& gam void GameListModel::UpdateGame(const std::shared_ptr& game) { - int index = FindGame(game->GetFilePath()); + int index = FindGameIndex(game->GetFilePath()); if (index < 0) { AddGame(game); @@ -292,7 +292,7 @@ void GameListModel::UpdateGame(const std::shared_ptr& void GameListModel::RemoveGame(const std::string& path) { - int entry = FindGame(path); + int entry = FindGameIndex(path); if (entry < 0) return; @@ -301,7 +301,13 @@ void GameListModel::RemoveGame(const std::string& path) endRemoveRows(); } -int GameListModel::FindGame(const std::string& path) const +std::shared_ptr GameListModel::FindGame(const std::string& path) const +{ + const int index = FindGameIndex(path); + return index < 0 ? nullptr : m_games[index]; +} + +int GameListModel::FindGameIndex(const std::string& path) const { for (int i = 0; i < m_games.size(); i++) { @@ -311,6 +317,29 @@ int GameListModel::FindGame(const std::string& path) const return -1; } +std::shared_ptr +GameListModel::FindSecondDisc(const UICommon::GameFile& game) const +{ + std::shared_ptr match_without_revision = nullptr; + + if (DiscIO::IsDisc(game.GetPlatform())) + { + for (auto& other_game : m_games) + { + if (game.GetGameID() == other_game->GetGameID() && + game.GetDiscNumber() != other_game->GetDiscNumber()) + { + if (game.GetRevision() == other_game->GetRevision()) + return other_game; + else + match_without_revision = other_game; + } + } + } + + return match_without_revision; +} + void GameListModel::SetSearchTerm(const QString& term) { m_term = term; diff --git a/Source/Core/DolphinQt/GameList/GameListModel.h b/Source/Core/DolphinQt/GameList/GameListModel.h index 7defb3f9cb..de0501d53b 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.h +++ b/Source/Core/DolphinQt/GameList/GameListModel.h @@ -63,6 +63,9 @@ public: void UpdateGame(const std::shared_ptr& game); void RemoveGame(const std::string& path); + std::shared_ptr FindGame(const std::string& path) const; + std::shared_ptr FindSecondDisc(const UICommon::GameFile& game) const; + void SetScale(float scale); float GetScale() const; @@ -79,7 +82,7 @@ public: private: // Index in m_games, or -1 if it isn't found - int FindGame(const std::string& path) const; + int FindGameIndex(const std::string& path) const; QStringList m_tag_list; QMap m_game_tags; diff --git a/Source/Core/DolphinQt/Main.cpp b/Source/Core/DolphinQt/Main.cpp index 4c7aa086ff..5ca745e874 100644 --- a/Source/Core/DolphinQt/Main.cpp +++ b/Source/Core/DolphinQt/Main.cpp @@ -147,7 +147,10 @@ int main(int argc, char* argv[]) std::unique_ptr boot; if (options.is_set("exec")) { - boot = BootParameters::GenerateFromFile(static_cast(options.get("exec"))); + const std::list paths_list = options.all("exec"); + const std::vector paths{std::make_move_iterator(std::begin(paths_list)), + std::make_move_iterator(std::end(paths_list))}; + boot = BootParameters::GenerateFromFile(paths); } else if (options.is_set("nand_title")) { diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 1b57cbe96c..ed4379ea98 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -168,6 +168,17 @@ static WindowSystemInfo GetWindowSystemInfo(QWindow* window) return wsi; } +static std::vector StringListToStdVector(QStringList list) +{ + std::vector result; + result.reserve(list.size()); + + for (const QString& s : list) + result.push_back(s.toStdString()); + + return result; +} + MainWindow::MainWindow(std::unique_ptr boot_parameters) : QMainWindow(nullptr) { setWindowTitle(QString::fromStdString(Common::scm_rev_str)); @@ -387,7 +398,7 @@ void MainWindow::ConnectMenuBar() connect(m_menu_bar, &MenuBar::EjectDisc, this, &MainWindow::EjectDisc); connect(m_menu_bar, &MenuBar::ChangeDisc, this, &MainWindow::ChangeDisc); connect(m_menu_bar, &MenuBar::BootDVDBackup, this, - [this](const QString& drive) { StartGame(drive); }); + [this](const QString& drive) { StartGame(drive, ScanForSecondDisc::No); }); // Emulation connect(m_menu_bar, &MenuBar::Pause, this, &MainWindow::Pause); @@ -610,30 +621,30 @@ void MainWindow::RefreshGameList() Settings::Instance().RefreshGameList(); } -QString MainWindow::PromptFileName() +QStringList MainWindow::PromptFileNames() { auto& settings = Settings::Instance().GetQSettings(); - QString path = QFileDialog::getOpenFileName( + QStringList paths = QFileDialog::getOpenFileNames( this, tr("Select a File"), settings.value(QStringLiteral("mainwindow/lastdir"), QStringLiteral("")).toString(), tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad *.dff);;" "All Files (*)")); - if (!path.isEmpty()) + if (!paths.isEmpty()) { settings.setValue(QStringLiteral("mainwindow/lastdir"), - QFileInfo(path).absoluteDir().absolutePath()); + QFileInfo(paths.front()).absoluteDir().absolutePath()); } - return path; + return paths; } void MainWindow::ChangeDisc() { - QString file = PromptFileName(); + std::vector paths = StringListToStdVector(PromptFileNames()); - if (!file.isEmpty()) - Core::RunAsCPUThread([&file] { DVDInterface::ChangeDisc(file.toStdString()); }); + if (!paths.empty()) + Core::RunAsCPUThread([&paths] { DVDInterface::ChangeDisc(paths); }); } void MainWindow::EjectDisc() @@ -643,9 +654,9 @@ void MainWindow::EjectDisc() void MainWindow::Open() { - QString file = PromptFileName(); - if (!file.isEmpty()) - StartGame(file); + QStringList files = PromptFileNames(); + if (!files.isEmpty()) + StartGame(StringListToStdVector(files)); } void MainWindow::Play(const std::optional& savestate_path) @@ -664,7 +675,7 @@ void MainWindow::Play(const std::optional& savestate_path) std::shared_ptr selection = m_game_list->GetSelectedGame(); if (selection) { - StartGame(selection->GetFilePath(), savestate_path); + StartGame(selection->GetFilePath(), ScanForSecondDisc::Yes, savestate_path); EnableScreenSaver(false); } else @@ -672,7 +683,7 @@ void MainWindow::Play(const std::optional& savestate_path) const QString default_path = QString::fromStdString(Config::Get(Config::MAIN_DEFAULT_ISO)); if (!default_path.isEmpty() && QFile::exists(default_path)) { - StartGame(default_path, savestate_path); + StartGame(default_path, ScanForSecondDisc::Yes, savestate_path); EnableScreenSaver(false); } else @@ -833,17 +844,46 @@ void MainWindow::ScreenShot() Core::SaveScreenShot(); } -void MainWindow::StartGame(const QString& path, const std::optional& savestate_path) +void MainWindow::ScanForSecondDiscAndStartGame(const UICommon::GameFile& game, + const std::optional& savestate_path) { - StartGame(path.toStdString(), savestate_path); + auto second_game = m_game_list->FindSecondDisc(game); + + std::vector paths = {game.GetFilePath()}; + if (second_game != nullptr) + paths.push_back(second_game->GetFilePath()); + + StartGame(paths, savestate_path); } -void MainWindow::StartGame(const std::string& path, +void MainWindow::StartGame(const QString& path, ScanForSecondDisc scan, const std::optional& savestate_path) { + StartGame(path.toStdString(), scan, savestate_path); +} + +void MainWindow::StartGame(const std::string& path, ScanForSecondDisc scan, + const std::optional& savestate_path) +{ + if (scan == ScanForSecondDisc::Yes) + { + std::shared_ptr game = m_game_list->FindGame(path); + if (game != nullptr) + { + ScanForSecondDiscAndStartGame(*game, savestate_path); + return; + } + } + StartGame(BootParameters::GenerateFromFile(path, savestate_path)); } +void MainWindow::StartGame(const std::vector& paths, + const std::optional& savestate_path) +{ + StartGame(BootParameters::GenerateFromFile(paths, savestate_path)); +} + void MainWindow::StartGame(std::unique_ptr&& parameters) { // If we're running, only start a new game once we've stopped the last. @@ -1075,7 +1115,7 @@ void MainWindow::ShowFIFOPlayer() { m_fifo_window = new FIFOPlayerWindow(this); connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this, - [this](const QString& path) { StartGame(path); }); + [this](const QString& path) { StartGame(path, ScanForSecondDisc::No); }); } m_fifo_window->show(); @@ -1170,7 +1210,7 @@ void MainWindow::NetPlayInit() #endif connect(m_netplay_dialog, &NetPlayDialog::Boot, this, - [this](const QString& path) { StartGame(path); }); + [this](const QString& path) { StartGame(path, ScanForSecondDisc::Yes); }); connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::ForceStop); connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); @@ -1346,38 +1386,48 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event) void MainWindow::dropEvent(QDropEvent* event) { - const auto& urls = event->mimeData()->urls(); + const QList& urls = event->mimeData()->urls(); if (urls.empty()) return; - const auto& url = urls[0]; - QFileInfo file_info(url.toLocalFile()); + QStringList files; + QStringList folders; - auto path = file_info.filePath(); - - if (!file_info.exists() || !file_info.isReadable()) + for (const QUrl& url : urls) { - QMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path)); - return; + QFileInfo file_info(url.toLocalFile()); + QString path = file_info.filePath(); + + if (!file_info.exists() || !file_info.isReadable()) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path)); + return; + } + + (file_info.isFile() ? files : folders).append(path); } - if (file_info.isFile()) + if (!files.isEmpty()) { - StartGame(path); + StartGame(StringListToStdVector(files)); } else { - auto& settings = Settings::Instance(); + Settings& settings = Settings::Instance(); + const bool show_confirm = settings.GetPaths().size() != 0; - if (settings.GetPaths().size() != 0) + for (const QString& folder : folders) { - if (QMessageBox::question( - this, tr("Confirm"), - tr("Do you want to add \"%1\" to the list of Game Paths?").arg(path)) != - QMessageBox::Yes) - return; + if (show_confirm) + { + if (QMessageBox::question( + this, tr("Confirm"), + tr("Do you want to add \"%1\" to the list of Game Paths?").arg(folder)) != + QMessageBox::Yes) + return; + } + settings.AddPath(folder); } - settings.AddPath(path); } } diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 30c29030db..4c5224fb6a 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -47,6 +48,11 @@ namespace DiscIO enum class Region; } +namespace UICommon +{ +class GameFile; +} + namespace X11Utils { class XRRConfiguration; @@ -115,8 +121,20 @@ private: void InitCoreCallbacks(); - void StartGame(const QString& path, const std::optional& savestate_path = {}); - void StartGame(const std::string& path, const std::optional& savestate_path = {}); + enum class ScanForSecondDisc + { + Yes, + No, + }; + + void ScanForSecondDiscAndStartGame(const UICommon::GameFile& game, + const std::optional& savestate_path = {}); + void StartGame(const QString& path, ScanForSecondDisc scan, + const std::optional& savestate_path = {}); + void StartGame(const std::string& path, ScanForSecondDisc scan, + const std::optional& savestate_path = {}); + void StartGame(const std::vector& paths, + const std::optional& savestate_path = {}); void StartGame(std::unique_ptr&& parameters); void ShowRenderWidget(); void HideRenderWidget(bool reinit = true); @@ -155,7 +173,7 @@ private: void ChangeDisc(); void EjectDisc(); - QString PromptFileName(); + QStringList PromptFileNames(); void EnableScreenSaver(bool enable); diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.cpp b/Source/Core/DolphinQt/Settings/GeneralPane.cpp index d98d5cb04e..78ec034fb3 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.cpp +++ b/Source/Core/DolphinQt/Settings/GeneralPane.cpp @@ -96,6 +96,7 @@ void GeneralPane::ConnectLayout() { connect(m_checkbox_dualcore, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); connect(m_checkbox_cheats, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); + connect(m_checkbox_auto_disc_change, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); #ifdef USE_DISCORD_PRESENCE connect(m_checkbox_discord_presence, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); #endif @@ -137,6 +138,9 @@ void GeneralPane::CreateBasic() m_checkbox_cheats = new QCheckBox(tr("Enable Cheats")); basic_group_layout->addWidget(m_checkbox_cheats); + m_checkbox_auto_disc_change = new QCheckBox(tr("Change Discs Automatically")); + basic_group_layout->addWidget(m_checkbox_auto_disc_change); + #ifdef USE_DISCORD_PRESENCE m_checkbox_discord_presence = new QCheckBox(tr("Show Current Game on Discord")); basic_group_layout->addWidget(m_checkbox_discord_presence); @@ -236,6 +240,7 @@ void GeneralPane::LoadConfig() #endif m_checkbox_dualcore->setChecked(SConfig::GetInstance().bCPUThread); m_checkbox_cheats->setChecked(Settings::Instance().GetCheatsEnabled()); + m_checkbox_auto_disc_change->setChecked(Config::Get(Config::MAIN_AUTO_DISC_CHANGE)); #ifdef USE_DISCORD_PRESENCE m_checkbox_discord_presence->setChecked(Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)); #endif @@ -295,6 +300,7 @@ void GeneralPane::OnSaveConfig() settings.bCPUThread = m_checkbox_dualcore->isChecked(); Config::SetBaseOrCurrent(Config::MAIN_CPU_THREAD, m_checkbox_dualcore->isChecked()); Settings::Instance().SetCheatsEnabled(m_checkbox_cheats->isChecked()); + Config::SetBase(Config::MAIN_AUTO_DISC_CHANGE, m_checkbox_auto_disc_change->isChecked()); Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, m_checkbox_cheats->isChecked()); settings.m_EmulationSpeed = m_combobox_speedlimit->currentIndex() * 0.1f; diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.h b/Source/Core/DolphinQt/Settings/GeneralPane.h index 90d53838e7..9ae0d48893 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.h +++ b/Source/Core/DolphinQt/Settings/GeneralPane.h @@ -44,6 +44,7 @@ private: QComboBox* m_combobox_update_track; QCheckBox* m_checkbox_dualcore; QCheckBox* m_checkbox_cheats; + QCheckBox* m_checkbox_auto_disc_change; #ifdef USE_DISCORD_PRESENCE QCheckBox* m_checkbox_discord_presence; #endif diff --git a/Source/Core/UICommon/CommandLineParse.cpp b/Source/Core/UICommon/CommandLineParse.cpp index bc8da4d92b..69f1491ad7 100644 --- a/Source/Core/UICommon/CommandLineParse.cpp +++ b/Source/Core/UICommon/CommandLineParse.cpp @@ -75,7 +75,7 @@ std::unique_ptr CreateParser(ParserOptions options) parser->add_option("-u", "--user").action("store").help("User folder path"); parser->add_option("-m", "--movie").action("store").help("Play a movie file"); parser->add_option("-e", "--exec") - .action("store") + .action("append") .metavar("") .type("string") .help("Load the specified file");