diff --git a/rpcs3/Emu/CPU/CPUThread.h b/rpcs3/Emu/CPU/CPUThread.h index b944d67d82..f52cd73ad8 100644 --- a/rpcs3/Emu/CPU/CPUThread.h +++ b/rpcs3/Emu/CPU/CPUThread.h @@ -179,7 +179,7 @@ public: // Callback for thread_ctrl::wait or RSX wait virtual void cpu_wait(bs_t old); - // Callback for function abortion stats on Emu.Stop() + // Callback for function abortion stats on Emu.Kill() virtual void cpu_on_stop() {} // For internal use diff --git a/rpcs3/Emu/Cell/Modules/cellSysutil.cpp b/rpcs3/Emu/Cell/Modules/cellSysutil.cpp index 5a9054ea5a..3b9a96c872 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysutil.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSysutil.cpp @@ -56,8 +56,10 @@ extern void sysutil_register_cb(std::function&& cb) cbm.registered.push(std::move(cb)); } -extern void sysutil_send_system_cmd(u64 status, u64 param) +extern u32 sysutil_send_system_cmd(u64 status, u64 param) { + u32 count = 0; + if (auto cbm = g_fxo->try_get()) { for (sysutil_cb_manager::registered_cb cb : cbm->callbacks) @@ -70,9 +72,13 @@ extern void sysutil_send_system_cmd(u64 status, u64 param) cb.first(ppu, status, param, cb.second); return CELL_OK; }); + + count++; } } } + + return count; } template <> diff --git a/rpcs3/Emu/Cell/Modules/cellSysutil.h b/rpcs3/Emu/Cell/Modules/cellSysutil.h index 0df8ec876f..c5b631ad4d 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysutil.h +++ b/rpcs3/Emu/Cell/Modules/cellSysutil.h @@ -301,5 +301,5 @@ struct CellSysCacheParam }; extern void sysutil_register_cb(std::function&&); -extern void sysutil_send_system_cmd(u64 status, u64 param); +extern u32 sysutil_send_system_cmd(u64 status, u64 param); s32 sysutil_check_name_string(const char* src, s32 minlen, s32 maxlen); diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index fa009d06a3..80065ed975 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -3405,7 +3405,7 @@ static void ppu_initialize2(jit_compiler& jit, const ppu_module& module_part, co { out.flush(); ppu_log.error("LLVM: Verification failed for %s:\n%s", obj_name, result); - Emu.CallAfter([]{ Emu.Stop(); }); + Emu.CallAfter([]{ Emu.GracefulShutdown(false, true); }); return; } diff --git a/rpcs3/Emu/Cell/lv2/sys_process.cpp b/rpcs3/Emu/Cell/lv2/sys_process.cpp index 90b4764128..511b871627 100644 --- a/rpcs3/Emu/Cell/lv2/sys_process.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_process.cpp @@ -349,7 +349,7 @@ void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3) Emu.CallAfter([]() { sys_process.success("Process finished"); - Emu.Stop(); + Emu.Kill(); }); // Wait for GUI thread @@ -415,8 +415,7 @@ void _sys_process_exit2(ppu_thread& ppu, s32 status, vm::ptr ar , hdd1 = std::move(hdd1), klic = g_fxo->get().last_key(), old_config = Emu.GetUsedConfig()]() mutable { sys_process.success("Process finished -> %s", argv[0]); - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.Kill(false); Emu.argv = std::move(argv); Emu.envp = std::move(envp); Emu.data = std::move(data); @@ -435,7 +434,7 @@ void _sys_process_exit2(ppu_thread& ppu, s32 status, vm::ptr ar if (res != game_boot_result::no_errors) { sys_process.fatal("Failed to boot from exitspawn! (path=\"%s\", error=%s)", path, res); - Emu.Stop(); + Emu.Kill(); } }); diff --git a/rpcs3/Emu/GDB.cpp b/rpcs3/Emu/GDB.cpp index e1c38de285..71b987249d 100644 --- a/rpcs3/Emu/GDB.cpp +++ b/rpcs3/Emu/GDB.cpp @@ -719,7 +719,7 @@ bool gdb_thread::cmd_attached_to_what(gdb_cmd&) bool gdb_thread::cmd_kill(gdb_cmd&) { GDB.notice("Kill command issued"); - Emu.Stop(); + Emu.CallAfter([](){ Emu.GracefulShutdown(); }); return true; } diff --git a/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog.cpp b/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog.cpp index 3ca6681791..90e04b2458 100644 --- a/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog.cpp @@ -21,7 +21,7 @@ namespace rsx Emu.CallAfter([]() { rsx_log.notice("Aborted shader loading dialog"); - Emu.Stop(); + Emu.Kill(false); }); }; diff --git a/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog_native.cpp b/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog_native.cpp index 99ec7750c8..288ea23e0f 100644 --- a/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog_native.cpp +++ b/rpcs3/Emu/RSX/Overlays/Shaders/shader_loading_dialog_native.cpp @@ -24,7 +24,7 @@ namespace rsx if (status != CELL_OK) { rsx_log.notice("Aborted shader loading dialog"); - Emu.Stop(); + Emu.Kill(false); } }); } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 90cd936055..76476fc2ca 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -558,7 +558,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool if (!IsStopped()) { - Stop(); + Kill(); } if (!title_id.empty()) @@ -886,8 +886,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool // Exit "process" CallAfter([] { - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.Kill(false); }); m_path = m_path_old; // Reset m_path to fix boot from gui @@ -1408,8 +1407,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool if (ppu_exec != elf_error::ok) { - SetForceBoot(true); - Stop(); + Kill(false); sys_log.error("Invalid or unsupported PPU executable format: %s", elf_path); @@ -1440,8 +1438,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool sys_log.warning("** ppu_prx -> %s", ppu_prx.get_error()); sys_log.warning("** spu_exec -> %s", spu_exec.get_error()); - SetForceBoot(true); - Stop(); + Kill(false); return game_boot_result::invalid_file_or_folder; } @@ -1458,8 +1455,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool return libs.count(std::string(lib.first) + ":lle") || (!lib.second && !libs.count(std::string(lib.first) + ":hle")); })) { - SetForceBoot(true); - Stop(); + Kill(false); CallAfter([this]() { @@ -1581,17 +1577,13 @@ bool Emulator::Pause(bool freeze_emulation) void Emulator::Resume() { - // Get pause start time - const u64 time = m_pause_start_time.exchange(0); - - // Try to increment summary pause time - if (time) + if (m_state != system_state::paused) { - m_pause_amend_time += get_system_time() - time; + return; } // Print and reset debug data collected - if (m_state == system_state::paused && g_cfg.core.ppu_debug) + if (g_cfg.core.ppu_debug) { PPUDisAsm dis_asm(cpu_disasm_mode::dump, vm::g_sudo_addr); @@ -1619,19 +1611,27 @@ void Emulator::Resume() ppu_log.notice("[RESUME] Dumping instruction stats:%s", dump); } - perf_stat_base::report(); - // Try to resume if (!m_state.compare_and_swap_test(system_state::paused, system_state::running)) { return; } - if (!time) + // Get pause start time + const u64 time = m_pause_start_time.exchange(0); + + // Try to increment summary pause time + if (time) + { + m_pause_amend_time += get_system_time() - time; + } + else { sys_log.error("Emulator::Resume() error: concurrent access"); } + perf_stat_base::report(); + auto on_select = [](u32, cpu_thread& cpu) { cpu.state -= cpu_flag::dbg_global_pause; @@ -1657,25 +1657,70 @@ void Emulator::Resume() } } -void Emulator::Stop(bool restart) +u32 sysutil_send_system_cmd(u64 status, u64 param); +void process_qt_events(); + +void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op) +{ + const auto old_state = m_state.load(); + + if (old_state == system_state::stopped) + { + return; + } + + if (old_state == system_state::paused) + { + Resume(); + } + + if (old_state == system_state::frozen || !sysutil_send_system_cmd(0x0101 /* CELL_SYSUTIL_REQUEST_EXITGAME */, 0)) + { + // The callback has been rudely ignored, we have no other option but to force termination + Kill(allow_autoexit); + return; + } + + auto perform_kill = [allow_autoexit, this, info = ProcureCurrentEmulationCourseInformation()]() + { + for (u32 i = 0; i < 50; i++) + { + std::this_thread::sleep_for(50ms); + Resume(); // TODO: Prevent pausing by other threads while in this loop + process_qt_events(); // Is nullified when performed on non-main thread + + if (static_cast(info) != m_stop_ctr) + { + return; + } + } + + // An inevitable attempt to terminate the *current* emulation course will be issued after 5s + CallAfter([allow_autoexit, this]() + { + Kill(allow_autoexit); + }, info); + }; + + if (async_op) + { + std::thread{perform_kill}.detach(); + } + else + { + perform_kill(); + } +} + +void Emulator::Kill(bool allow_autoexit) { if (m_state.exchange(system_state::stopped) == system_state::stopped) { - if (restart) - { - // Reload with prior configs. - if (const auto error = Load(m_title_id); error != game_boot_result::no_errors) - sys_log.error("Restart failed: %s", error); - return; - } - - m_force_boot = false; return; } sys_log.notice("Stopping emulator..."); - if (!restart) { // Show visual feedback to the user in case that stopping takes a while. // This needs to be done before actually stopping, because otherwise the necessary threads will be terminated before we can show an image. @@ -1783,15 +1828,7 @@ void Emulator::Stop(bool restart) m_stop_ctr++; m_stop_ctr.notify_all(); - if (restart) - { - // Reload with prior configs. - if (const auto error = Load(m_title_id); error != game_boot_result::no_errors) - sys_log.error("Restart failed: %s", error); - return; - } - - // Boot arg cleanup (preserved in the case restarting) + // Boot arg cleanup argv.clear(); envp.clear(); data.clear(); @@ -1804,15 +1841,36 @@ void Emulator::Stop(bool restart) // Always Enable display sleep, not only if it was prevented. enable_display_sleep(); - if (!m_force_boot) + if (allow_autoexit) { if (Quit(g_cfg.misc.autoexit.get())) { return; } } +} - m_force_boot = false; +game_boot_result Emulator::Restart() +{ + if (m_state == system_state::stopped) + { + return game_boot_result::generic_error; + } + + auto save_args = std::make_tuple(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_mode); + + GracefulShutdown(false, false); + + std::tie(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_mode) = std::move(save_args); + + // Reload with prior configs. + if (const auto error = Load(m_title_id); error != game_boot_result::no_errors) + { + sys_log.error("Restart failed: %s", error); + return error; + } + + return game_boot_result::no_errors; } bool Emulator::Quit(bool force_quit) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index f0159f9786..b76793da11 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -111,9 +111,9 @@ class Emulator final std::string m_usr{"00000001"}; u32 m_usrid{1}; - // This flag should be adjusted before each Stop() or each BootGame() and similar because: + // This flag should be adjusted before each Kill() or each BootGame() and similar because: // 1. It forces an application to boot immediately by calling Run() in Load(). - // 2. It signifies that we don't want to exit on Stop(), for example if we want to transition to another application. + // 2. It signifies that we don't want to exit on Kill(), for example if we want to transition to another application. bool m_force_boot = false; bool m_has_gui = true; @@ -132,14 +132,14 @@ public: } // Call from the GUI thread - void CallAfter(std::function&& func, bool track_emu_state = true) const + void CallAfter(std::function&& func, bool track_emu_state = true, u64 stop_ctr = umax) const { if (!track_emu_state) { return m_cb.call_after(std::move(func)); } - std::function final_func = [this, before = IsStopped(), count = +m_stop_ctr, func = std::move(func)] + std::function final_func = [this, before = IsStopped(), count = (stop_ctr == umax ? +m_stop_ctr : stop_ctr), func = std::move(func)] { if (count == m_stop_ctr && before == IsStopped()) { @@ -150,6 +150,18 @@ public: return m_cb.call_after(std::move(final_func)); } + enum class stop_counter_t : u64{}; + + stop_counter_t ProcureCurrentEmulationCourseInformation() const + { + return stop_counter_t{+m_stop_ctr}; + } + + void CallAfter(std::function&& func, stop_counter_t counter) const + { + CallAfter(std::move(func), true, static_cast(counter)); + } + /** Set emulator mode to running unconditionnaly. * Required to execute various part (PPUInterpreter, memory manager...) outside of rpcs3. */ @@ -244,8 +256,9 @@ public: void Run(bool start_playtime); bool Pause(bool freeze_emulation = false); void Resume(); - void Stop(bool restart = false); - void Restart() { Stop(true); } + void GracefulShutdown(bool allow_autoexit = true, bool async_op = false); + void Kill(bool allow_autoexit = true); + game_boot_result Restart(); bool Quit(bool force_quit); static void CleanUp(); diff --git a/rpcs3/Emu/system_progress.cpp b/rpcs3/Emu/system_progress.cpp index f5248dad7b..472fc0ceff 100644 --- a/rpcs3/Emu/system_progress.cpp +++ b/rpcs3/Emu/system_progress.cpp @@ -92,7 +92,7 @@ void progress_dialog_server::operator()() { // Abort everything sys_log.notice("Aborted progress dialog"); - Emu.Stop(); + Emu.GracefulShutdown(false, true); }); g_system_progress_canceled = true; diff --git a/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp b/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp index cbf56d7d29..03186fe828 100644 --- a/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp @@ -62,8 +62,7 @@ auto_pause_settings_dialog::auto_pause_settings_dialog(QWidget *parent) : QDialo }); connect(cancelButton, &QAbstractButton::clicked, this, &QWidget::close); - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); LoadEntries(); UpdateList(); diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 6638fea3ff..1dbfa46abc 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -1402,8 +1402,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) bool game_list_frame::CreatePPUCache(const std::string& path, const std::string& serial) { - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); Emu.SetForceBoot(true); if (const auto error = Emu.BootGame(path, serial, true); error != game_boot_result::no_errors) @@ -1701,7 +1700,7 @@ void game_list_frame::BatchCreatePPUCaches() if (!Emu.IsStopped()) { QApplication::processEvents(); - Emu.Stop(); + Emu.GracefulShutdown(false); } if (!pdlg->wasCanceled()) diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 7556ef8271..e814b07152 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -363,7 +363,7 @@ void gs_frame::close() if (!Emu.IsStopped()) { - Emu.Stop(); + Emu.GracefulShutdown(true, false); } deleteLater(); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index ab3a74b042..7bacdf4947 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -69,6 +69,14 @@ std::shared_ptr make_basic_ppu_disasm(); inline std::string sstr(const QString& _in) { return _in.toStdString(); } +extern void process_qt_events() +{ + if (thread_ctrl::is_main()) + { + QApplication::processEvents(); + } +} + main_window::main_window(std::shared_ptr gui_settings, std::shared_ptr emu_settings, std::shared_ptr persistent_settings, QWidget *parent) : QMainWindow(parent) , ui(new Ui::main_window) @@ -189,7 +197,7 @@ bool main_window::Init(bool with_cli_boot) connect(m_thumb_stop, &QWinThumbnailToolButton::clicked, this, []() { gui_log.notice("User clicked stop button on thumbnail toolbar"); - Emu.Stop(); + Emu.GracefulShutdown(false, true); }); connect(m_thumb_restart, &QWinThumbnailToolButton::clicked, this, []() { @@ -399,10 +407,9 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo return; } - m_app_icon = gui::utils::get_app_icon_from_path(path, title_id); + Emu.GracefulShutdown(false); - Emu.SetForceBoot(true); - Emu.Stop(); + m_app_icon = gui::utils::get_app_icon_from_path(path, title_id); if (const auto error = Emu.BootGame(path, title_id, direct, add_only, config_mode, config_path); error != game_boot_result::no_errors) { @@ -524,8 +531,7 @@ void main_window::BootRsxCapture(std::string path) return; } - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); if (!Emu.BootRsxCapture(path)) { @@ -651,6 +657,17 @@ void main_window::InstallPackages(QStringList file_paths) } } + if (!m_gui_settings->GetBootConfirmation(this)) + { + // Last chance to cancel the operation + return; + } + + if (!Emu.IsStopped()) + { + Emu.GracefulShutdown(false); + } + // Install rap files if available int installed_rap_and_edat_count = 0; @@ -732,6 +749,8 @@ void main_window::HandlePackageInstallation(QStringList file_paths) return; } + Emu.GracefulShutdown(false); + progress_dialog pdlg(tr("RPCS3 Package Installer"), tr("Installing package, please wait..."), tr("Cancel"), 0, 1000, false, this); pdlg.setAutoClose(false); pdlg.show(); @@ -774,9 +793,6 @@ void main_window::HandlePackageInstallation(QStringList file_paths) pdlg.setLabelText(tr("Installing package (%0/%1), please wait...\n\n%2").arg(i + 1).arg(count).arg(app_info)); pdlg.show(); - Emu.SetForceBoot(true); - Emu.Stop(); - const QFileInfo file_info(package.path); const std::string path = sstr(package.path); const std::string file_name = sstr(file_info.fileName()); @@ -934,8 +950,7 @@ void main_window::ExtractTar() return; } - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); const QString path_last_tar = m_gui_settings->GetValue(gui::fd_ext_tar).toString(); QStringList files = QFileDialog::getOpenFileNames(this, tr("Select TAR To extract"), path_last_tar, tr("All tar files (*.tar *.TAR *.tar.aa.* *.TAR.AA.*);;All files (*.*)")); @@ -1011,8 +1026,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString& return; } - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); m_gui_settings->SetValue(gui::fd_install_pup, QFileInfo(file_path).path()); @@ -1257,7 +1271,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString& } // This is ugly, but PS3 headers shall not be included there. -extern void sysutil_send_system_cmd(u64 status, u64 param); +extern u32 sysutil_send_system_cmd(u64 status, u64 param); void main_window::DecryptSPRXLibraries() { @@ -2026,6 +2040,8 @@ void main_window::CreateConnects() if (!paths.isEmpty()) { + Emu.GracefulShutdown(false); + for (const QString& path : paths) { AddGamesFromDir(path); @@ -2087,7 +2103,7 @@ void main_window::CreateConnects() connect(ui->sysStopAct, &QAction::triggered, this, []() { gui_log.notice("User triggered stop action in menu bar"); - Emu.Stop(); + Emu.GracefulShutdown(false, true); }); connect(ui->sysRebootAct, &QAction::triggered, this, []() { @@ -2415,7 +2431,7 @@ void main_window::CreateConnects() connect(ui->toolbar_stop, &QAction::triggered, this, []() { gui_log.notice("User triggered stop action in toolbar"); - Emu.Stop(); + Emu.GracefulShutdown(false); }); connect(ui->toolbar_start, &QAction::triggered, this, &main_window::OnPlayOrPause); @@ -2762,8 +2778,7 @@ void main_window::CreateFirmwareCache() return; } - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); Emu.SetForceBoot(true); if (const game_boot_result error = Emu.BootGame(g_cfg_vfs.get_dev_flash() + "sys", "", true); @@ -2811,8 +2826,9 @@ void main_window::closeEvent(QCloseEvent* closeEvent) // Cleanly stop and quit the emulator. if (!Emu.IsStopped()) { - Emu.Stop(); + Emu.GracefulShutdown(false); } + Emu.Quit(true); } @@ -3007,10 +3023,13 @@ void main_window::dropEvent(QDropEvent* event) { return; } + for (const auto& path : drop_paths) { + Emu.GracefulShutdown(false); AddGamesFromDir(path); } + m_game_list_frame->Refresh(true); break; } @@ -3021,8 +3040,7 @@ void main_window::dropEvent(QDropEvent* event) return; } - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); if (const auto error = Emu.BootGame(sstr(drop_paths.first()), "", true); error != game_boot_result::no_errors) { diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp index 309ba30cc6..56273f7929 100644 --- a/rpcs3/rpcs3qt/update_manager.cpp +++ b/rpcs3/rpcs3qt/update_manager.cpp @@ -655,8 +655,7 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept) QMessageBox::information(m_parent, tr("Auto-updater"), tr("Update successful!\nRPCS3 will now restart.")); } - Emu.SetForceBoot(true); - Emu.Stop(); + Emu.GracefulShutdown(false); Emu.CleanUp(); #ifdef _WIN32 diff --git a/rpcs3/rpcs3qt/user_manager_dialog.cpp b/rpcs3/rpcs3qt/user_manager_dialog.cpp index b78c1fdc33..bbbfe74a65 100644 --- a/rpcs3/rpcs3qt/user_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/user_manager_dialog.cpp @@ -361,7 +361,7 @@ void user_manager_dialog::OnUserLogin() } gui_log.notice("Stopping current emulation in order to change the current user."); - Emu.Stop(); + Emu.GracefulShutdown(false); } const u32 key = GetUserKey();