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();