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
This commit is contained in:
Exzap 2026-06-25 06:42:08 +02:00
parent a3d6395ed7
commit 53a74e44df
8 changed files with 84 additions and 21 deletions

View File

@ -443,6 +443,7 @@ namespace CafeSystem
static SystemImplementation* s_implementation{nullptr};
bool sLaunchModeIsStandalone = false;
std::optional<std::vector<std::string>> s_overrideArgs;
std::optional<sint32> 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<sint32> GetForegroundTitleReturnStatus()
{
return s_foregroundReturnStatus;
}
}

View File

@ -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<sint32> 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;

View File

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

View File

@ -15,13 +15,13 @@ void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters,
// coreinit shared memory
struct CoreinitSharedData
{
MEMPTR<void> MEMAllocFromDefaultHeap;
MEMPTR<void> MEMAllocFromDefaultHeapEx;
MEMPTR<void> MEMFreeToDefaultHeap;
MPTR __atexit_cleanup;
MPTR __cpp_exception_init_ptr;
MPTR __cpp_exception_cleanup_ptr;
MPTR __stdio_cleanup;
MEMPTR<void> MEMAllocFromDefaultHeap{nullptr};
MEMPTR<void> MEMAllocFromDefaultHeapEx{nullptr};
MEMPTR<void> MEMFreeToDefaultHeap{nullptr};
MEMPTR<void> __atexit_cleanup{nullptr};
MEMPTR<void> __stdio_cleanup{nullptr};
MPTR __cpp_exception_init_ptr{0};
MPTR __cpp_exception_cleanup_ptr{0};
};
extern CoreinitSharedData* gCoreinitData;

View File

@ -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<char>* 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);
}
};

View File

@ -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
}

View File

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

View File

@ -157,8 +157,10 @@ private:
// CafeSystem implementation
void CafeRecreateCanvas() override;
void CafePPCProcessExit() override;
void OnRequestRecreateCanvas(wxCommandEvent& event);
void OnRequestGameExit(wxCommandEvent& event);
wxRect GetDesktopRect();