diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index b7b07412d..539552e59 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -886,21 +886,37 @@ GameList::Entry* GameList::GetMutableEntryForPath(std::string_view path) const GameList::Entry* GameList::GetEntryBySerial(std::string_view serial) { + const Entry* fallback_entry = nullptr; + for (const Entry& entry : s_entries) { - if (entry.serial == serial) - return &entry; + if (!entry.IsDiscSet() && entry.serial == serial) + { + // prefer actual discs + if (!entry.IsDisc()) + fallback_entry = fallback_entry ? fallback_entry : &entry; + else + return &entry; + } } - return nullptr; + return fallback_entry; } const GameList::Entry* GameList::GetEntryBySerialAndHash(std::string_view serial, u64 hash) { + const Entry* fallback_entry = nullptr; + for (const Entry& entry : s_entries) { - if (entry.serial == serial && entry.hash == hash) - return &entry; + if (!entry.IsDiscSet() && entry.serial == serial && entry.hash == hash) + { + // prefer actual discs + if (!entry.IsDisc()) + fallback_entry = fallback_entry ? fallback_entry : &entry; + else + return &entry; + } } return nullptr; diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index 0206f78cf..add452c66 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -250,6 +250,20 @@ DEFINE_NON_ANDROID_HOTKEY("ChangeDisc", TRANSLATE_NOOP("Hotkeys", "System"), TRA FullscreenUI::OpenDiscChangeMenu(); }) +DEFINE_HOTKEY("SwitchToPreviousDisc", TRANSLATE_NOOP("Hotkeys", "System"), + TRANSLATE_NOOP("Hotkeys", "Switch to Previous Disc"), [](s32 pressed) { + // Defer because otherwise the hotkey might be invalidated by config change. + if (!pressed) + Host::RunOnCPUThread([]() { System::SwitchToPreviousDisc(true); }); + }) + +DEFINE_HOTKEY("SwitchToNextDisc", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Switch to Next Disc"), + [](s32 pressed) { + // Defer because otherwise the hotkey might be invalidated by config change. + if (!pressed) + Host::RunOnCPUThread([]() { System::SwitchToNextDisc(true); }); + }) + DEFINE_HOTKEY("Rewind", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Rewind"), [](s32 pressed) { if (pressed < 0) return; diff --git a/src/core/system.cpp b/src/core/system.cpp index 36dd1fa05..267a7c092 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -192,6 +192,7 @@ static bool UpdateGameSettingsLayer(); static void UpdateInputSettingsLayer(std::string input_profile_name, std::unique_lock& lock); static void UpdateRunningGame(const std::string& path, CDImage* image, bool booting); static bool CheckForRequiredSubQ(Error* error); +static bool SwitchDiscFromSet(s32 direction, bool show_osd_message); static void UpdateControllers(); static void ResetControllers(); @@ -3510,7 +3511,7 @@ void System::FormatLatencyStats(SmallStringBase& str) const double pre_frame_time = std::ceil(Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time)); const double input_latency = std::ceil( Timer::ConvertValueToMilliseconds((s_state.frame_period - s_state.pre_frame_sleep_time) * - static_cast(std::max(queued_frame_count, 1u))) - + (std::max(queued_frame_count, 1u))) - Timer::ConvertValueToMilliseconds(static_cast(s_state.runahead_frames) * s_state.frame_period)); str.format("AL: {}ms | AF: {:.0f}ms | PF: {:.0f}ms | IL: {:.0f}ms | QF: {}", audio_latency, active_frame_time, @@ -4337,6 +4338,84 @@ bool System::SwitchMediaSubImage(u32 index) return true; } +bool System::SwitchDiscFromSet(s32 direction, bool display_osd_message) +{ + if (!IsValid() || !s_state.running_game_entry || s_state.running_game_entry->disc_set_serials.empty()) + { + if (display_osd_message) + { + Host::AddIconOSDWarning("SwitchDiscFromSet", ICON_EMOJI_WARNING, + TRANSLATE_STR("System", "Current game does not have multiple discs."), + Host::OSD_WARNING_DURATION); + } + + return false; + } + + s32 current_index = static_cast(s_state.running_game_entry->disc_set_serials.size()); + for (size_t i = 0; i < s_state.running_game_entry->disc_set_serials.size(); i++) + { + if (s_state.running_game_entry->disc_set_serials[i] == s_state.running_game_serial) + { + current_index = static_cast(i); + break; + } + } + + if (current_index == static_cast(s_state.running_game_entry->disc_set_serials.size())) + { + if (display_osd_message) + { + Host::AddIconOSDWarning("SwitchDiscFromSet", ICON_EMOJI_WARNING, + TRANSLATE_STR("System", "Could not determine current disc for switching."), + Host::OSD_WARNING_DURATION); + } + + return false; + } + + current_index += direction; + if (current_index < 0 || current_index >= static_cast(s_state.running_game_entry->disc_set_serials.size())) + { + if (display_osd_message) + { + Host::AddIconOSDWarning("SwitchDiscFromSet", ICON_EMOJI_WARNING, + (direction < 0) ? TRANSLATE_STR("System", "There is no previous disc to switch to.") : + TRANSLATE_STR("System", "There is no next disc to switch to."), + Host::OSD_WARNING_DURATION); + } + + return false; + } + + const std::string_view& next_serial = s_state.running_game_entry->disc_set_serials[current_index]; + const auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryBySerial(next_serial); + if (!entry) + { + if (display_osd_message) + { + Host::AddIconOSDWarning("SwitchDiscFromSet", ICON_EMOJI_WARNING, + fmt::format(TRANSLATE_FS("System", "No disc found for serial {}."), next_serial), + Host::OSD_WARNING_DURATION); + } + + return false; + } + + return InsertMedia(entry->path.c_str()); +} + +bool System::SwitchToPreviousDisc(bool display_osd_message) +{ + return SwitchDiscFromSet(-1, display_osd_message); +} + +bool System::SwitchToNextDisc(bool display_osd_message) +{ + return SwitchDiscFromSet(1, display_osd_message); +} + bool System::ShouldStartFullscreen() { return Host::GetBoolSettingValue("Main", "StartFullscreen", false); diff --git a/src/core/system.h b/src/core/system.h index 359caaba2..51bd2f13e 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -329,6 +329,10 @@ std::string GetMediaSubImageTitle(u32 index); /// Switches to the specified media/disc playlist index. bool SwitchMediaSubImage(u32 index); +/// Switches to the previous/next disc in the disc set, if any. +bool SwitchToPreviousDisc(bool display_osd_message); +bool SwitchToNextDisc(bool display_osd_message); + /// Updates throttler. void UpdateSpeedLimiterState();