From 896c4ab7daa70ddb36d0b7daadbc48e025d9e8df Mon Sep 17 00:00:00 2001 From: Cynthia Date: Tue, 23 Jun 2026 11:17:45 -0700 Subject: [PATCH] allow `coreinit.exit` to actually "exit" fixes #1960 this commit implements one particular approach for exiting emulation when the RPX being emulated calls `coreinit.exit`. I don't believe this might be the best way long term, but it seemed to be the best way given the state (and my knowledge of the codebase). This threads through 'End Emulation' (normally a debug only feature in the Cemu menu) into one more spot in the codebase, when `coreinit.exit` is called. Why did we go this way? Stopping PPC Emulation _while_ the emulator is actively executing an instruction (the call to `coreinit.exit`) seemed like a path of el derado everytime i tried it. calling `Shutdown`, or `ShutdownTitle` is not ideal because it doesn't do things like update status in discord or any of the other window actions, and they lock up because the emulator is already running the coreinit instructions (even if you launch it in a delayed thread, the PPC emulator is still doing stuff). so the only way to get this to somewhat work is _to reach out to the window system_. So I threaded through an `EndEmulation` request, which started off just calling `EndEmulation` that already existed. There is a note on that function about how it memory leaks if called repeatedly, and it's "not finished". However, this seemed better than looping forever, or just crashing with a debug assert. At least you still have a semi-functional window (even if memory is leaking), and a log window you can investigate? However, just calling endemulation didn't exit when Cemu was launched from the command line (my goal), it just returns to the main Cemu window. To make Cemu usable as a command line app that you can fire and forget, I threaded a new variable through, that when end emulation is called and the title was launched with `-g`, or `-t`, it just closes cemu altogether. this allows my CLI commands to "just work" --- src/Cafe/CafeSystem.cpp | 29 ++++++++------ src/Cafe/CafeSystem.h | 3 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit.cpp | 7 ++-- src/gui/interface/WindowSystem.h | 4 ++ src/gui/wxgui/CemuApp.cpp | 5 ++- src/gui/wxgui/MainWindow.cpp | 38 +++++++++++++++++++ src/gui/wxgui/MainWindow.h | 13 +++++++ src/gui/wxgui/components/wxGameList.cpp | 5 +++ src/gui/wxgui/components/wxGameList.h | 2 +- src/gui/wxgui/wxWindowSystem.cpp | 16 ++++++++ src/main.cpp | 6 +-- 12 files changed, 107 insertions(+), 23 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 20a76812..593a171a 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -650,22 +650,23 @@ namespace CafeSystem s_implementation = impl; } - void Shutdown() - { - cemu_assert_debug(s_initialized); - // if a title is running, shut it down - if (sSystemRunning) - ShutdownTitle(); - // shutdown persistent subsystems (deprecated manual shutdown) + void Shutdown() + { + if (!s_initialized) + return; + // if a title is running, shut it down + if (sSystemRunning) + ShutdownTitle(); + // shutdown persistent subsystems (deprecated manual shutdown) iosu::odm::Shutdown(); iosu::act::Stop(); - iosu::mcp::Shutdown(); - iosu::fsa::Shutdown(); + iosu::mcp::Shutdown(); + iosu::fsa::Shutdown(); // shutdown IOSU modules for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it) (*it)->SystemExit(); - s_initialized = false; - } + s_initialized = false; + } std::string GetInternalVirtualCodeFolder() { @@ -1088,6 +1089,12 @@ namespace CafeSystem sSystemRunning = false; } + void RequestEndTitle(int exitCode) + { + WindowSystem::SetExitCode(exitCode); + WindowSystem::RequestEndCurrentEmulation(); + } + /* Virtual mlc storage */ void InitVirtualMlcStorage() diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index 4003b393..e134877c 100644 --- a/src/Cafe/CafeSystem.h +++ b/src/Cafe/CafeSystem.h @@ -23,7 +23,7 @@ namespace CafeSystem void Initialize(); void SetImplementation(SystemImplementation* impl); - void Shutdown(); + void Shutdown(); PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId); PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path); @@ -45,6 +45,7 @@ namespace CafeSystem CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group); void ShutdownTitle(); + void RequestEndTitle(int exitCode); std::string GetMlcStoragePath(TitleId titleId); void MlcStorageMountAllTitles(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index b4d99e35..e92ffb97 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -364,7 +364,7 @@ int BreathOfTheWildChildProcessMain() vkEnumeratePhysicalDevices(instance, &count, nullptr); vkDestroyInstance(instance, nullptr); - return 0; + return WindowSystem::GetExitCode(); } static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanceCreateInfo* create_info) diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index fd3ed23b..24206db1 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -2,6 +2,7 @@ #include "Common/SysAllocator.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "Cafe/CafeSystem.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" // includes for Initialize coreinit submodules @@ -241,10 +242,8 @@ namespace coreinit void coreinit_exit(uint32 r) { cemuLog_log(LogType::Force, "The title terminated the process by calling coreinit.exit({})", (sint32)r); - DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); - cemu_assert_debug(false); - // never return - while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100)); + DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); + CafeSystem::RequestEndTitle((int) r); } bool OSIsOffBoot() diff --git a/src/gui/interface/WindowSystem.h b/src/gui/interface/WindowSystem.h index b2346c17..1cd52108 100644 --- a/src/gui/interface/WindowSystem.h +++ b/src/gui/interface/WindowSystem.h @@ -98,6 +98,10 @@ namespace WindowSystem } void Create(); + int GetExitCode(); + void SetExitCode(int value); + + void RequestEndCurrentEmulation(); WindowInfo& GetWindowInfo(); diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index 9a636989..4c22ac1b 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -1,4 +1,5 @@ #include "wxgui/CemuApp.h" +#include "interface/WindowSystem.h" #include "wxCemuConfig.h" #include "wxgui/MainWindow.h" #include "wxgui/wxgui.h" @@ -422,9 +423,9 @@ int CemuApp::OnExit() SDLControllerProvider::ShutdownSDL(); #endif #if BOOST_OS_WINDOWS - ExitProcess(0); + ExitProcess(WindowSystem::GetExitCode()); #else - _Exit(0); + _Exit(WindowSystem::GetExitCode()); #endif } diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index 542554f2..014854d3 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -161,6 +161,7 @@ enum wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_REQUEST_GAMELIST_REFRESH, wxCommandEvent); wxDEFINE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); +wxDEFINE_EVENT(wxEVT_END_EMULATION, wxEndEmulationEvent); wxDEFINE_EVENT(wxEVT_REQUEST_RECREATE_CANVAS, wxCommandEvent); wxBEGIN_EVENT_TABLE(MainWindow, wxFrame) @@ -324,11 +325,13 @@ MainWindow::MainWindow() if (load_file) { + m_launched_bare = true; MainWindow::RequestLaunchGame(load_file.value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); quick_launch = true; } else if (load_title_id) { + m_launched_bare = true; TitleInfo info; TitleId baseId; if (CafeTitleList::FindBaseTitleId(load_title_id.value(), baseId) && CafeTitleList::GetFirstByTitleId(baseId, info)) @@ -370,6 +373,7 @@ MainWindow::MainWindow() Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this); Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this); + Bind(wxEVT_END_EMULATION, &MainWindow::OnEndEmulation, this); if (LaunchSettings::GDBStubEnabled()) { @@ -625,6 +629,34 @@ void MainWindow::OnLaunchFromFile(wxLaunchGameEvent& event) FileLoad(event.GetPath(), event.GetInitiatedBy()); } +void MainWindow::OnEndEmulation(wxEndEmulationEvent& event) +{ + // Game launched with `-g`, or `-t`, no window to return to. + if (m_launched_bare) + { + if (m_debugger_window) + { + m_debugger_window->CleanupForDestroy(); + m_debugger_window->Destroy(); + m_debugger_window = nullptr; + } + + if(m_game_list) + m_game_list->Close(); + + if (!IsMaximized() && !WindowSystem::IsFullScreen()) + m_restored_size = GetSize(); + + SaveSettings(); + m_timer->Stop(); + + CafeSystem::Shutdown(); + Destroy(); + } + else + EndEmulation(); +} + void MainWindow::OnFileMenu(wxCommandEvent& event) { const auto menuId = event.GetId(); @@ -2452,6 +2484,12 @@ void MainWindow::RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIAT wxPostEvent(g_mainFrame, evt); } +void MainWindow::RequestEndEmulation() +{ + wxEndEmulationEvent evt; + wxPostEvent(g_mainFrame, evt); +} + void MainWindow::OnRequestRecreateCanvas(wxCommandEvent& event) { CounterSemaphore* sem = (CounterSemaphore*)event.GetClientData(); diff --git a/src/gui/wxgui/MainWindow.h b/src/gui/wxgui/MainWindow.h index eec043b8..a4c46ecc 100644 --- a/src/gui/wxgui/MainWindow.h +++ b/src/gui/wxgui/MainWindow.h @@ -23,8 +23,10 @@ class TitleManager; class GraphicPacksWindow2; class EmulatedUSBDeviceFrame; class wxLaunchGameEvent; +class wxEndEmulationEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); +wxDECLARE_EVENT(wxEVT_END_EMULATION, wxEndEmulationEvent); wxDECLARE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); class wxLaunchGameEvent : public wxCommandEvent @@ -52,6 +54,14 @@ private: INITIATED_BY m_initiatedBy; }; +class wxEndEmulationEvent : public wxCommandEvent +{ +public: + wxEndEmulationEvent() : wxCommandEvent(wxEVT_END_EMULATION) {} + + wxEvent* Clone() const { return new wxEndEmulationEvent(*this); } +}; + class MainWindow : public wxFrame, public CafeSystem::SystemImplementation { friend class CemuApp; @@ -132,6 +142,7 @@ public: void OnGesturePan(wxPanGestureEvent& event); void OnGameLoaded(); + void OnEndEmulation(wxEndEmulationEvent& event); void AsyncSetTitle(std::string_view windowTitle); @@ -144,6 +155,7 @@ public: static void RequestGameListRefresh(); static void RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy); + static void RequestEndEmulation(); private: bool FullscreenEnabled() const; @@ -176,6 +188,7 @@ private: bool m_menu_visible = false; bool m_game_launched = false; + bool m_launched_bare = false; #ifdef ENABLE_DISCORD_RPC std::unique_ptr m_discord; diff --git a/src/gui/wxgui/components/wxGameList.cpp b/src/gui/wxgui/components/wxGameList.cpp index a1e5142d..b8ee5d32 100644 --- a/src/gui/wxgui/components/wxGameList.cpp +++ b/src/gui/wxgui/components/wxGameList.cpp @@ -1077,6 +1077,11 @@ void wxGameList::OnColumnResize(wxListEvent& event) void wxGameList::OnClose(wxCloseEvent& event) { event.Skip(); + Close(); +} + +void wxGameList::Close() +{ m_exit = true; } diff --git a/src/gui/wxgui/components/wxGameList.h b/src/gui/wxgui/components/wxGameList.h index 388a9df6..5ed62bb9 100644 --- a/src/gui/wxgui/components/wxGameList.h +++ b/src/gui/wxgui/components/wxGameList.h @@ -57,6 +57,7 @@ public: long FindListItemByTitleId(uint64 title_id) const; void OnClose(wxCloseEvent& event); + void Close(); private: std::atomic_bool m_exit = false; @@ -163,4 +164,3 @@ private: static inline std::once_flag s_launch_file_once; }; - diff --git a/src/gui/wxgui/wxWindowSystem.cpp b/src/gui/wxgui/wxWindowSystem.cpp index bb730db8..4665a1de 100644 --- a/src/gui/wxgui/wxWindowSystem.cpp +++ b/src/gui/wxgui/wxWindowSystem.cpp @@ -30,6 +30,7 @@ WindowSystem::WindowInfo g_window_info{}; +int g_exitCode = 0; std::shared_mutex g_mutex; MainWindow* g_mainFrame = nullptr; @@ -56,6 +57,21 @@ void WindowSystem::Create() #endif } +int WindowSystem::GetExitCode() +{ + return g_exitCode; +} + +void WindowSystem::SetExitCode(int value) +{ + g_exitCode = value; +} + +void WindowSystem::RequestEndCurrentEmulation() +{ + MainWindow::RequestEndEmulation(); +} + void WindowSystem::ShowErrorDialog(std::string_view message, std::string_view title, std::optional /*errorId*/) { wxString caption; diff --git a/src/main.cpp b/src/main.cpp index 9b9e04c2..2b485e3a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -245,7 +245,7 @@ int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int if (!LaunchSettings::HandleCommandline(lpCmdLine)) return 0; WindowSystem::Create(); - return 0; + return WindowSystem::GetExitCode(); } // entrypoint for debug builds with console @@ -259,7 +259,7 @@ int main(int argc, char* argv[]) if (!LaunchSettings::HandleCommandline(argc, argv)) return 0; WindowSystem::Create(); - return 0; + return WindowSystem::GetExitCode(); } #else @@ -278,7 +278,7 @@ int main(int argc, char *argv[]) if (!LaunchSettings::HandleCommandline(argc, argv)) return 0; WindowSystem::Create(); - return 0; + return WindowSystem::GetExitCode(); } #endif