From 53a74e44df53865e9471f8139e5dd93518f7da14 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 25 Jun 2026 06:42:08 +0200 Subject: [PATCH] coreinit+UI: Implement exit() When coreinit exit() is hit, then Cemu will only close when the game was launched directly via command line parameters (-g or -t). Otherwise it returns to the game list. In the case where Cemu closes it will forward the coreinit exit status code as it's own exit status code. This is useful for cases where Cemu is used as a CLI tool for testing homebrew --- src/Cafe/CafeSystem.cpp | 13 +++++++++ src/Cafe/CafeSystem.h | 4 +++ src/Cafe/OS/libs/coreinit/coreinit.cpp | 10 ------- src/Cafe/OS/libs/coreinit/coreinit.h | 14 +++++----- src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp | 30 ++++++++++++++++++++- src/gui/wxgui/CemuApp.cpp | 9 ++++--- src/gui/wxgui/MainWindow.cpp | 23 ++++++++++++++++ src/gui/wxgui/MainWindow.h | 2 ++ 8 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 20a76812..e52b8a7c 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -443,6 +443,7 @@ namespace CafeSystem static SystemImplementation* s_implementation{nullptr}; bool sLaunchModeIsStandalone = false; std::optional> s_overrideArgs; + std::optional s_foregroundReturnStatus; bool sSystemRunning = false; TitleId sForegroundTitleId = 0; @@ -836,6 +837,7 @@ namespace CafeSystem { CafeTitleList::WaitForMandatoryScan(); sLaunchModeIsStandalone = false; + s_foregroundReturnStatus = std::nullopt; _pathToExecutable.clear(); TitleIdParser tip(titleId); if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC || tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) @@ -1186,4 +1188,15 @@ namespace CafeSystem s_implementation->CafeRecreateCanvas(); } + void NotifyPPCProcessExit(sint32 status) + { + s_foregroundReturnStatus = status; + s_implementation->CafePPCProcessExit(); + } + + std::optional GetForegroundTitleReturnStatus() + { + return s_foregroundReturnStatus; + } + } diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index 4003b393..ddf7621c 100644 --- a/src/Cafe/CafeSystem.h +++ b/src/Cafe/CafeSystem.h @@ -12,6 +12,7 @@ namespace CafeSystem { public: virtual void CafeRecreateCanvas() = 0; + virtual void CafePPCProcessExit() = 0; // emulated process exited }; enum class PREPARE_STATUS_CODE @@ -43,6 +44,7 @@ namespace CafeSystem std::string GetForegroundTitleArgStr(); uint32 GetForegroundTitleOlvAccesskey(); CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group); + std::optional GetForegroundTitleReturnStatus(); // valid once the foreground title exited gracefully via coreinit exit void ShutdownTitle(); @@ -55,6 +57,8 @@ namespace CafeSystem uint32 GetRPXHashUpdated(); void RequestRecreateCanvas(); + void NotifyPPCProcessExit(sint32 status); + }; extern RPLModule* applicationRPX; diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index fd3ed23b..4d454e0e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -238,15 +238,6 @@ namespace coreinit osLib_returnFromFunction(hCPU, 1); } - 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)); - } - bool OSIsOffBoot() { return true; @@ -289,7 +280,6 @@ namespace coreinit osLib_addFunction("coreinit", "ENVGetEnvironmentVariable", coreinitExport_ENVGetEnvironmentVariable); - cafeExportRegisterFunc(coreinit_exit, "coreinit", "exit", LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsOffBoot, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetBootPMFlags, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetSystemMode, LogType::CoreinitThread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit.h b/src/Cafe/OS/libs/coreinit/coreinit.h index 2aa3f1df..e7b88821 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.h +++ b/src/Cafe/OS/libs/coreinit/coreinit.h @@ -15,13 +15,13 @@ void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, // coreinit shared memory struct CoreinitSharedData { - MEMPTR MEMAllocFromDefaultHeap; - MEMPTR MEMAllocFromDefaultHeapEx; - MEMPTR MEMFreeToDefaultHeap; - MPTR __atexit_cleanup; - MPTR __cpp_exception_init_ptr; - MPTR __cpp_exception_cleanup_ptr; - MPTR __stdio_cleanup; + MEMPTR MEMAllocFromDefaultHeap{nullptr}; + MEMPTR MEMAllocFromDefaultHeapEx{nullptr}; + MEMPTR MEMFreeToDefaultHeap{nullptr}; + MEMPTR __atexit_cleanup{nullptr}; + MEMPTR __stdio_cleanup{nullptr}; + MPTR __cpp_exception_init_ptr{0}; + MPTR __cpp_exception_cleanup_ptr{0}; }; extern CoreinitSharedData* gCoreinitData; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index 820f336c..6a6cafe0 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -693,7 +693,6 @@ namespace coreinit return s_sdkVersion; } - // move this to CafeSystem.cpp? void OSLauncherThread(uint64 titleId) { CafeSystem::ShutdownTitle(); @@ -702,6 +701,12 @@ namespace coreinit CafeSystem::LaunchForegroundTitle(); } + void OSShutdownThread(sint32 status) + { + CafeSystem::ShutdownTitle(); + CafeSystem::NotifyPPCProcessExit(status); + } + uint32 __LaunchByTitleId(uint64 titleId, uint32 argc, MEMPTR* argv) { // prepare argument buffer @@ -851,6 +856,26 @@ namespace coreinit return 0; } + void __PPCExit(sint32 status) + { + // spawn shutdown thread (the current thread has to be destroyed as part of the shutdown process) + std::thread shutdownThread(OSShutdownThread, status); + shutdownThread.detach(); + // suspend this thread + coreinit::OSSuspendThread(coreinit::OSGetCurrentThread()); + } + + void coreinit_exit(sint32 status) + { + cemuLog_log(LogType::Force, "The title terminated the process by calling coreinit.exit({})", (sint32)status); + DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); + if (gCoreinitData->__atexit_cleanup) + PPCCoreCallback(gCoreinitData->__atexit_cleanup, status); + if (gCoreinitData->__stdio_cleanup) + PPCCoreCallback(gCoreinitData->__stdio_cleanup); + __PPCExit(status); + } + void miscInit() { s_currentTitleId = CafeSystem::GetForegroundTitleId(); @@ -886,6 +911,9 @@ namespace coreinit cafeExportRegister("coreinit", OSDriver_Register, LogType::Placeholder); cafeExportRegister("coreinit", OSDriver_Deregister, LogType::Placeholder); + + cafeExportRegisterFunc(coreinit_exit, "coreinit", "exit", LogType::CoreinitThread); + cafeExportRegister("coreinit", __PPCExit, LogType::CoreinitThread); } }; diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index 9a636989..bb7b6c7c 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -34,6 +34,7 @@ #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/SaveList.h" +#include "Cafe/CafeSystem.h" wxIMPLEMENT_APP_NO_MAIN(CemuApp); @@ -414,17 +415,19 @@ int CemuApp::OnExit() m_sdlEventPumpTimer = nullptr; } #endif - wxApp::OnExit(); wxTheClipboard->Flush(); InputManager::instance().Shutdown(); + int retValue = 0; + if (auto r = CafeSystem::GetForegroundTitleReturnStatus(); (LaunchSettings::GetLoadFile() || LaunchSettings::GetLoadTitleID()) && r) + retValue = *r; #if BOOST_OS_MACOS SDLControllerProvider::ShutdownSDL(); #endif #if BOOST_OS_WINDOWS - ExitProcess(0); + ExitProcess(retValue); #else - _Exit(0); + _Exit(retValue); #endif } diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index 542554f2..e1e88ff8 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -162,6 +162,7 @@ wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_REQUEST_GAMELIST_REFRESH, wxCommandEvent); wxDEFINE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); wxDEFINE_EVENT(wxEVT_REQUEST_RECREATE_CANVAS, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_REQUEST_GAME_EXIT, wxCommandEvent); wxBEGIN_EVENT_TABLE(MainWindow, wxFrame) EVT_TIMER(MAINFRAME_ID_TIMER1, MainWindow::OnTimer) @@ -243,6 +244,7 @@ EVT_COMMAND(wxID_ANY, wxEVT_ACCOUNTLIST_REFRESH, MainWindow::OnAccountListRefres EVT_COMMAND(wxID_ANY, wxEVT_SET_WINDOW_TITLE, MainWindow::OnSetWindowTitle) EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_RECREATE_CANVAS, MainWindow::OnRequestRecreateCanvas) +EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_GAME_EXIT, MainWindow::OnRequestGameExit) wxEND_EVENT_TABLE() @@ -2469,6 +2471,27 @@ void MainWindow::CafeRecreateCanvas() sem.decrementWithWait(); } +void MainWindow::CafePPCProcessExit() +{ + // this is called from the emulated PPC thread, so queue an event instead of handling it directly + wxQueueEvent(g_mainFrame, new wxCommandEvent(wxEVT_REQUEST_GAME_EXIT)); +} + +void MainWindow::OnRequestGameExit(wxCommandEvent& event) +{ + // if the title was launched via the command line we close Cemu and use the foreground title's exit status as Cemu's process return code (via CemuApp:OnExit) + // this is useful for homebrew testing setups that use Cemu via CLI + // otherwise the title was launched from the game list, so we just stop it and return to the game list instead of closing Cemu + if (LaunchSettings::GetLoadFile() || LaunchSettings::GetLoadTitleID()) + { + Close(); + } + else + { + EndEmulation(); + } +} + bool MainWindow::FullscreenEnabled() const { return LaunchSettings::FullscreenEnabled().value_or(GetWxGUIConfig().fullscreen); diff --git a/src/gui/wxgui/MainWindow.h b/src/gui/wxgui/MainWindow.h index eec043b8..e1cfc8b5 100644 --- a/src/gui/wxgui/MainWindow.h +++ b/src/gui/wxgui/MainWindow.h @@ -157,8 +157,10 @@ private: // CafeSystem implementation void CafeRecreateCanvas() override; + void CafePPCProcessExit() override; void OnRequestRecreateCanvas(wxCommandEvent& event); + void OnRequestGameExit(wxCommandEvent& event); wxRect GetDesktopRect();