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"
This commit is contained in:
Cynthia 2026-06-23 11:17:45 -07:00
parent a3d6395ed7
commit 896c4ab7da
12 changed files with 107 additions and 23 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -98,6 +98,10 @@ namespace WindowSystem
}
void Create();
int GetExitCode();
void SetExitCode(int value);
void RequestEndCurrentEmulation();
WindowInfo& GetWindowInfo();

View File

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

View File

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

View File

@ -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<DiscordPresence> m_discord;

View File

@ -1077,6 +1077,11 @@ void wxGameList::OnColumnResize(wxListEvent& event)
void wxGameList::OnClose(wxCloseEvent& event)
{
event.Skip();
Close();
}
void wxGameList::Close()
{
m_exit = true;
}

View File

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

View File

@ -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<WindowSystem::ErrorCategory> /*errorId*/)
{
wxString caption;

View File

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