diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 363768d6719ff..01a7ca6cfc370 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -391,48 +391,14 @@ void EmuThread::run() // Main CPU thread loop. while (!m_shutdown_flag.load()) - { - if (!VMManager::HasValidVM()) - { - m_event_loop->exec(); - continue; - } - - executeVM(); - } - - // Teardown in reverse order. - stopBackgroundControllerPollTimer(); - destroyBackgroundControllerPollTimer(); - VMManager::Internal::CPUThreadShutdown(); - - // Move back to the UI thread, since we're no longer running. - moveToThread(m_ui_thread); - deleteLater(); -} - -void EmuThread::destroyVM() -{ - m_last_speed = 0.0f; - m_last_game_fps = 0.0f; - m_last_video_fps = 0.0f; - m_last_internal_width = 0; - m_last_internal_height = 0; - m_was_paused_by_focus_loss = false; - VMManager::Shutdown(m_save_state_on_shutdown); - m_save_state_on_shutdown = false; -} - -void EmuThread::executeVM() -{ - for (;;) { switch (VMManager::GetState()) { case VMState::Initializing: - pxFailRel("Shouldn't be in the starting state state"); + pxFailRel("Shouldn't be in the starting state"); continue; + case VMState::Shutdown: case VMState::Paused: m_event_loop->exec(); continue; @@ -448,13 +414,33 @@ void EmuThread::executeVM() case VMState::Stopping: destroyVM(); - m_event_loop->processEvents(QEventLoop::AllEvents); - return; + continue; default: continue; } } + + // Teardown in reverse order. + stopBackgroundControllerPollTimer(); + destroyBackgroundControllerPollTimer(); + VMManager::Internal::CPUThreadShutdown(); + + // Move back to the UI thread, since we're no longer running. + moveToThread(m_ui_thread); + deleteLater(); +} + +void EmuThread::destroyVM() +{ + m_last_speed = 0.0f; + m_last_game_fps = 0.0f; + m_last_video_fps = 0.0f; + m_last_internal_width = 0; + m_last_internal_height = 0; + m_was_paused_by_focus_loss = false; + VMManager::Shutdown(m_save_state_on_shutdown); + m_save_state_on_shutdown = false; } void EmuThread::createBackgroundControllerPollTimer() diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h index 5f88aaafdddee..9a50b171daa42 100644 --- a/pcsx2-qt/QtHost.h +++ b/pcsx2-qt/QtHost.h @@ -194,7 +194,6 @@ public Q_SLOTS: static constexpr u32 FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL = 8; void destroyVM(); - void executeVM(); void createBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer(); diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index 23f3536ecfab9..7a2e4a0b31694 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -1825,6 +1825,43 @@ bool Achievements::ConfirmHardcoreModeDisable(const char* trigger) return true; } +void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::function callback) +{ +#ifdef ENABLE_RAINTEGRATION + if (IsUsingRAIntegration()) + { + const bool result = (RA_WarnDisableHardcore(trigger) != 0); + callback(result); + return; + } +#endif + + if (!FullscreenUI::Initialize()) + { + Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Cannot {} while hardcode mode is active.", trigger)), + Host::OSD_WARNING_DURATION); + callback(false); + return; + } + + auto real_callback = [callback = std::move(callback)](bool res) mutable { + // don't run the callback in the middle of rendering the UI + Host::RunOnCPUThread([callback = std::move(callback), res]() { + if (res) + DisableHardcoreMode(); + callback(res); + }); + }; + + ImGuiFullscreen::OpenConfirmMessageDialog( + TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"), + fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you " + "want to disable hardcore mode? {0} will be cancelled if you select No."), + trigger), + std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")), + fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No"))); +} + void Achievements::ClearUIState() { if (FullscreenUI::IsAchievementsWindowOpen() || FullscreenUI::IsLeaderboardsWindowOpen()) diff --git a/pcsx2/Achievements.h b/pcsx2/Achievements.h index 0cb8fb087cab2..37bc4069a6e9d 100644 --- a/pcsx2/Achievements.h +++ b/pcsx2/Achievements.h @@ -19,6 +19,7 @@ #include "Config.h" +#include #include #include #include @@ -83,6 +84,7 @@ namespace Achievements /// Prompts the user to disable hardcore mode, if they agree, returns true. bool ConfirmHardcoreModeDisable(const char* trigger); + void ConfirmHardcoreModeDisableAsync(const char* trigger, std::function callback); /// Returns true if hardcore mode is active, and functionality should be restricted. bool IsHardcoreModeActive(); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 0fcfc4dd49dd5..3a8502f8b9969 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -855,17 +855,12 @@ void FullscreenUI::DoStartPath(const std::string& path, std::optional state params.fast_boot = fast_boot; // switch to nothing, we'll get brought back if init fails - const MainWindowType prev_window = s_current_main_window; - s_current_main_window = MainWindowType::None; - - Host::RunOnCPUThread([params = std::move(params), prev_window]() { + Host::RunOnCPUThread([params = std::move(params)]() { if (VMManager::HasValidVM()) return; if (VMManager::Initialize(std::move(params))) VMManager::SetState(VMState::Running); - else - s_current_main_window = prev_window; }); } @@ -5514,6 +5509,18 @@ void FullscreenUI::DrawGameListWindow() default: break; } + + if (VMManager::GetState() != VMState::Shutdown) + { + // Dummy window to prevent interacting with the game list while loading. + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowBgAlpha(0.25f); + ImGui::Begin("##dummy", nullptr, ImGuiWindowFlags_NoDecoration); + ImGui::End(); + ImGui::PopStyleColor(); + } } void FullscreenUI::DrawGameList(const ImVec2& heading_size) diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 987979d1c1d9f..a5d43545fdc89 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -527,7 +527,7 @@ void VMManager::ApplyGameFixes() game->applyGameFixes(EmuConfig, EmuConfig.EnableGameFixes); game->applyGSHardwareFixes(EmuConfig.GS); - + // Re-remove upscaling fixes, make sure they don't apply at native res. // We do this in LoadCoreSettings(), but game fixes get applied afterwards because of the unsafe warning. EmuConfig.GS.MaskUpscalingHacks(); @@ -658,7 +658,7 @@ void VMManager::Internal::UpdateEmuFolders() AutoEject::SetAll(); - if(!GSDumpReplayer::IsReplayingDump()) + if (!GSDumpReplayer::IsReplayingDump()) FileMcd_Reopen(memcardFilters.empty() ? s_disc_serial : memcardFilters); } @@ -1085,6 +1085,12 @@ bool VMManager::Initialize(VMBootParameters boot_params) s_elf_override = {}; ClearELFInfo(); ClearDiscDetails(); + + Achievements::GameChanged(0, 0); + FullscreenUI::GameChanged(s_title, std::string(), s_disc_serial, 0, 0); + UpdateDiscordPresence(); + Host::OnGameChanged(s_title, std::string(), std::string(), s_disc_serial, 0, 0); + UpdateGameSettingsLayer(); s_state.store(VMState::Shutdown, std::memory_order_release); Host::OnVMDestroyed(); @@ -1184,7 +1190,6 @@ bool VMManager::Initialize(VMBootParameters boot_params) if (!FileSystem::FileExists(s_elf_override.c_str())) { Host::ReportErrorAsync("Error", fmt::format("Requested boot ELF '{}' does not exist.", s_elf_override)); - DoCDVDclose(); return false; } @@ -1200,11 +1205,34 @@ bool VMManager::Initialize(VMBootParameters boot_params) } // Check for resuming with hardcore mode. - Achievements::ResetHardcoreMode(); - if (!state_to_load.empty() && Achievements::IsHardcoreModeActive() && - !Achievements::ConfirmHardcoreModeDisable(TRANSLATE("VMManager", "Resuming state"))) + // Why do we need the boot param? Because we need some way of telling BootSystem() that + // the user allowed HC mode to be disabled, because otherwise we'll ResetHardcoreMode() + // and send ourselves into an infinite loop. + if (boot_params.disable_achievements_hardcore_mode) + Achievements::DisableHardcoreMode(); + else + Achievements::ResetHardcoreMode(); + if (!state_to_load.empty() && Achievements::IsHardcoreModeActive()) { - return false; + if (FullscreenUI::IsInitialized()) + { + boot_params.elf_override = std::move(s_elf_override); + boot_params.save_state = std::move(state_to_load); + boot_params.disable_achievements_hardcore_mode = true; + s_elf_override = {}; + + Achievements::ConfirmHardcoreModeDisableAsync(TRANSLATE("VMManager", "Resuming state"), + [boot_params = std::move(boot_params)](bool approved) mutable { + if (approved && Initialize(std::move(boot_params))) + SetState(VMState::Running); + }); + + return false; + } + else if (!Achievements::ConfirmHardcoreModeDisable(TRANSLATE("VMManager", "Resuming state"))) + { + return false; + } } s_limiter_mode = GetInitialLimiterMode(); @@ -1233,7 +1261,7 @@ bool VMManager::Initialize(VMBootParameters boot_params) } ScopedGuard close_spu2(&SPU2::Close); - + Console.WriteLn("Initializing Pad..."); if (!Pad::Initialize()) { @@ -1706,8 +1734,15 @@ u32 VMManager::DeleteSaveStates(const char* game_serial, u32 game_crc, bool also bool VMManager::LoadState(const char* filename) { - if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable(TRANSLATE("VMManager", "Loading state"))) + if (Achievements::IsHardcoreModeActive()) + { + Achievements::ConfirmHardcoreModeDisableAsync(TRANSLATE("VMManager", "Loading state"), + [filename = std::string(filename)](bool approved) { + if (approved) + LoadState(filename.c_str()); + }); return false; + } // TODO: Save the current state so we don't need to reset. if (DoLoadState(filename)) @@ -1719,7 +1754,7 @@ bool VMManager::LoadState(const char* filename) bool VMManager::LoadStateFromSlot(s32 slot) { - const std::string filename(GetCurrentSaveStateFileName(slot)); + const std::string filename = GetCurrentSaveStateFileName(slot); if (filename.empty()) { Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE, @@ -1728,8 +1763,15 @@ bool VMManager::LoadStateFromSlot(s32 slot) return false; } - if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable(TRANSLATE("VMManager", "Loading state"))) + if (Achievements::IsHardcoreModeActive()) + { + Achievements::ConfirmHardcoreModeDisableAsync(TRANSLATE("VMManager", "Loading state"), + [slot](bool approved) { + if (approved) + LoadStateFromSlot(slot); + }); return false; + } Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN, fmt::format(TRANSLATE_FS("VMManager", "Loading state from slot {}..."), slot), Host::OSD_QUICK_DURATION); @@ -1916,8 +1958,16 @@ void VMManager::FrameAdvance(u32 num_frames /*= 1*/) if (!HasValidVM()) return; - if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable(TRANSLATE("VMManager", "Frame advancing"))) + if (Achievements::IsHardcoreModeActive()) + { + Achievements::ConfirmHardcoreModeDisableAsync(TRANSLATE("VMManager", "Frame advancing"), + [num_frames](bool approved) { + if (approved) + FrameAdvance(num_frames); + }); + return; + } s_frame_advance_count = num_frames; SetState(VMState::Running); @@ -1961,7 +2011,7 @@ bool VMManager::ChangeDisc(CDVD_SourceType source, std::string path) if (!DoCDVDopen(&error)) { Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, - fmt::format(TRANSLATE_FS("VMManager", "Failed to switch back to old disc image. Removing disc.\nError was: {}"), + fmt::format(TRANSLATE_FS("VMManager", "Failed to switch back to old disc image. Removing disc.\nError was: {}"), error.GetDescription()), Host::OSD_CRITICAL_ERROR_DURATION); CDVDsys_ChangeSource(CDVD_SourceType::NoDisc); @@ -2263,7 +2313,7 @@ void VMManager::CheckForGSConfigChanges(const Pcsx2Config& old_config) } else if (EmuConfig.GS.VsyncEnable != old_config.GS.VsyncEnable) { - // Still need to update target speed, because of sync-to-host-refresh. + // Still need to update target speed, because of sync-to-host-refresh. UpdateTargetSpeed(); } @@ -2482,8 +2532,7 @@ void VMManager::WarnAboutUnsafeSettings() return; std::string messages; - auto append = [&messages](const char* icon, const std::string_view& msg) - { + auto append = [&messages](const char* icon, const std::string_view& msg) { messages += icon; messages += ' '; messages += msg; @@ -2860,11 +2909,12 @@ static void InitializeCPUInfo() s_big_cores = 0; s_small_cores = 0; std::vector classes = DarwinMisc::GetCPUClasses(); - for (size_t i = 0; i < classes.size(); i++) { + for (size_t i = 0; i < classes.size(); i++) + { const DarwinMisc::CPUClass& cls = classes[i]; const bool is_big = i == 0 || i < classes.size() - 1; // Assume only one group is small DevCon.WriteLn("(VMManager) Found %u physical cores and %u logical cores in perf level %u (%s), assuming %s", - cls.num_physical, cls.num_logical, i, cls.name.c_str(), is_big ? "big" : "small"); + cls.num_physical, cls.num_logical, i, cls.name.c_str(), is_big ? "big" : "small"); (is_big ? s_big_cores : s_small_cores) += cls.num_physical; } } diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index db724d4553d46..68b961be7db3f 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -47,6 +47,7 @@ struct VMBootParameters std::optional fast_boot; std::optional fullscreen; + bool disable_achievements_hardcore_mode = false; }; namespace VMManager