Merge branch 'cemu-project:main' into main

This commit is contained in:
Eddy 2026-06-26 10:47:13 +02:00 committed by GitHub
commit f0ccc3fbf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 231 additions and 54 deletions

47
.gdbinit Normal file
View File

@ -0,0 +1,47 @@
python
import gdb
import re
class BetypePrinter:
PATTERN = re.compile("^betype<.*>$")
def __init__(self, obj):
self._obj = obj
def to_string(self):
underlying = self._obj["m_value"]
reversed_bytes = bytes(reversed(underlying.bytes))
return gdb.Value(reversed_bytes, underlying.type)
class MemptrPrinter:
PATTERN = re.compile("^MEMPTR<.*>$")
def __init__(self, obj):
self._ptr_type = obj.type.strip_typedefs().unqualified().template_argument(0).pointer()
self._guest_address = obj["m_value"].cast(gdb.lookup_type("uint32"))
def ptr(self):
if self._guest_address == 0:
return gdb.Value(0, self._ptr_type)
base_addr = gdb.parse_and_eval('memory_base', global_context=True)
return (self._guest_address + base_addr).reinterpret_cast(self._ptr_type)
def children(self):
return [("raw", self.ptr())]
def to_string(self):
return self._guest_address.format_string(format="x")
def lookup(val):
tag = val.type.strip_typedefs().unqualified().tag
if tag is None:
return None
printers = [BetypePrinter, MemptrPrinter]
for printer in printers:
if printer.PATTERN.match(tag):
return printer(val)
return None
gdb.pretty_printers.append(lookup)

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;
@ -685,6 +686,22 @@ namespace CafeSystem
fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
}
void MountExtras()
{
for (const auto& [host_path, emulatedPath] : LaunchSettings::CosMounts())
{
FSCDeviceHostFS_Mount(boost::nowide::narrow(emulatedPath), _pathToUtf8(host_path), FSC_PRIORITY_BASE);
}
}
void UnmountExtras()
{
for (const auto& [_, emulatedPath] : LaunchSettings::CosMounts())
{
fsc_unmount(boost::nowide::narrow(emulatedPath), FSC_PRIORITY_BASE);
}
}
PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
{
cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
@ -820,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)
@ -838,6 +856,7 @@ namespace CafeSystem
if (r != PREPARE_STATUS_CODE::SUCCESS)
return r;
InitVirtualMlcStorage();
MountExtras();
return PREPARE_STATUS_CODE::SUCCESS;
}
@ -881,6 +900,7 @@ namespace CafeSystem
// load executable
PrepareExecutable();
InitVirtualMlcStorage();
MountExtras();
return PREPARE_STATUS_CODE::SUCCESS;
}
@ -960,6 +980,9 @@ namespace CafeSystem
std::string GetForegroundTitleArgStr()
{
auto optional_arguments = LaunchSettings::CosArgstr();
if (optional_arguments.has_value())
return *optional_arguments;
if (sLaunchModeIsStandalone)
return "";
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
@ -1044,24 +1067,26 @@ namespace CafeSystem
{
if(!sSystemRunning)
return;
coreinit::OSSchedulerEnd();
Latte_Stop();
// reset Cafe OS userspace modules
snd_core::reset();
coreinit::OSAlarm_Shutdown();
GX2::_GX2DriverReset();
nn::save::ResetToDefaultState();
coreinit::__OSDeleteAllActivePPCThreads();
RPLLoader_UnloadAll();
coreinit::OSSchedulerEnd();
Latte_Stop();
// reset Cafe OS userspace modules
snd_core::reset();
coreinit::OSAlarm_Shutdown();
GX2::_GX2DriverReset();
nn::save::ResetToDefaultState();
coreinit::__OSDeleteAllActivePPCThreads();
RPLLoader_UnloadAll();
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
(*it)->TitleStop();
// reset Cemu subsystems
PPCRecompiler_Shutdown();
GraphicPack2::Reset();
UnmountCurrentTitle();
MlcStorageUnmountAllTitles();
UnmountBaseDirectories();
DestroyMemorySpace();
// reset Cemu subsystems
PPCRecompiler_Shutdown();
GraphicPack2::Reset();
UnmountCurrentTitle();
UnmountExtras();
MlcStorageUnmountAllTitles();
UnmountBaseDirectories();
DestroyMemorySpace();
LaunchSettings::ClearCosArgstr();
sSystemRunning = false;
}
@ -1163,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

@ -200,7 +200,7 @@ bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
if (dataSource->readData(0, 0, 0x18000 + 0x100, header, sizeof(header)) != sizeof(header))
return false;
// try all the keys
// try all the keys in the key cache
uint8 headerDecrypted[sizeof(header)-16];
for (sint32 i = 0; i < 0x7FFFFFFF; i++)
{
@ -215,7 +215,12 @@ bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
return true;
}
}
return false;
// check for a key file next to the WUD file
fs::path keyPath = path;
keyPath.replace_extension("key");
std::unique_ptr<FileStream> keyFile(FileStream::openFile2(keyPath));
return keyFile && keyFile->readData(discTitleKey.b, sizeof(discTitleKey.b)) == sizeof(discTitleKey.b);
}
// open WUD image using key cache

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

@ -4,6 +4,7 @@
#include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/Filesystem/fsc.h"
#include "config/LaunchSettings.h"
#include <pugixml.hpp>
namespace coreinit
@ -544,6 +545,7 @@ namespace coreinit
}
std::mutex sCafeConsoleMutex;
bool s_forwardConsoleLogs;
void WriteCafeConsole(CafeLogType cafeLogType, const char* msg, sint32 len)
{
@ -556,6 +558,14 @@ namespace coreinit
cafeLogBuffer.lineLength = 0;
};
if (s_forwardConsoleLogs) {
if (cafeLogType == CafeLogType::OSCONSOLE) {
fwrite(msg, 1, len, stdout);
fflush(stdout);
} else {
fwrite(msg, 1, len, stderr);
}
}
while (len)
{
char c = *msg;
@ -684,7 +694,6 @@ namespace coreinit
return s_sdkVersion;
}
// move this to CafeSystem.cpp?
void OSLauncherThread(uint64 titleId)
{
CafeSystem::ShutdownTitle();
@ -693,6 +702,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
@ -842,10 +857,31 @@ 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();
s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion();
s_forwardConsoleLogs = LaunchSettings::ForwardConsoleLogging();
s_transitionToBackground = false;
s_transitionToForeground = false;
@ -876,6 +912,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

@ -63,7 +63,7 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args)
#endif
("game,g", po::wvalue<std::wstring>(), "Path of game to launch")
("title-id,t", po::value<std::string>(), "Title ID of the title to be launched (overridden by --game)")
("title-id,t", po::value<std::string>(), "Title ID of the title to be launched (overridden by --game)")
("mlc,m", po::wvalue<std::wstring>(), "Custom mlc folder location")
("fullscreen,f", po::value<bool>()->implicit_value(true), "Launch games in fullscreen mode")
@ -71,6 +71,10 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args)
("account,a", po::value<std::string>(), "Persistent id of account")
("cos-mounts", po::wvalue<std::vector<std::wstring>>()->composing(), "A series of mounts in the form of: (path on host:path within emulated system, e.g. `/tmp:/vol/temporary/`)")
("forward-console-logging", "Forward OSReport, OSConsoleWrite, etc. to stdout/stderr.")
("cos-argstr", po::value<std::string>(), "A custom argstr used to override to the arguments to the first RPX that is launched, will be unset after the first launch.")
("force-interpreter", po::value<bool>()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler. Useful for debugging purposes where you want to get accurate memory accesses and stack traces.")
("force-multicore-interpreter", po::value<bool>()->implicit_value(true), "Force multi-core interpreter CPU emulation, disables recompiler. Only useful for getting stack traces, but slightly faster than the single-core interpreter mode.")
("enable-gdbstub", po::value<bool>()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger");
@ -189,6 +193,30 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args)
if (vm.count("enable-gdbstub"))
s_enable_gdbstub = vm["enable-gdbstub"].as<bool>();
if (vm.count("forward-console-logging"))
{
requireConsole();
s_forward_console_logging = true;
}
if (vm.count("cos-argstr"))
s_cos_argstr = vm["cos-argstr"].as<std::string>();
if (vm.count("cos-mounts"))
{
for (const auto& argument : vm["cos-mounts"].as<std::vector<std::wstring>>())
{
size_t colon_location = argument.find(L':');
if (colon_location == std::wstring::npos)
{
std::cerr << "Argument for a mount expects to be in the format: `path on host:path in emulated system`, was not: `" << boost::nowide::narrow(argument) << "`\n";
continue;
}
s_cos_mounts[argument.substr(0, colon_location)] = argument.substr(colon_location + 1);
}
}
std::wstring extract_path, log_path;
std::string output_path;
if (vm.count("extract"))

View File

@ -2,6 +2,7 @@
#include <optional>
#include <string>
#include <unordered_map>
class LaunchSettings
{
@ -16,7 +17,7 @@ public:
static bool HandleCommandline(const std::vector<std::wstring>& args);
static std::optional<fs::path> GetLoadFile() { return s_load_game_file; }
static std::optional<uint64> GetLoadTitleID() {return s_load_title_id;}
static std::optional<uint64> GetLoadTitleID() {return s_load_title_id;}
static std::optional<fs::path> GetMLCPath() { return s_mlc_path; }
static std::optional<bool> RenderUpsideDownEnabled() { return s_render_upside_down; }
@ -24,6 +25,11 @@ public:
static bool Verbose() { return s_verbose; }
static bool ForwardConsoleLogging() { return s_forward_console_logging; }
static std::optional<std::string> CosArgstr() { return s_cos_argstr; }
static void ClearCosArgstr() { s_cos_argstr.reset(); }
static std::unordered_map<fs::path, std::wstring>& CosMounts() { return s_cos_mounts; }
static bool GDBStubEnabled() { return s_enable_gdbstub; }
static bool NSightModeEnabled() { return s_nsight_mode; }
@ -37,14 +43,18 @@ public:
private:
inline static std::optional<fs::path> s_load_game_file{};
inline static std::optional<uint64> s_load_title_id{};
inline static std::optional<uint64> s_load_title_id{};
inline static std::optional<fs::path> s_mlc_path{};
inline static std::optional<bool> s_render_upside_down{};
inline static std::optional<bool> s_fullscreen{};
inline static bool s_verbose = false;
inline static bool s_forward_console_logging = false;
inline static std::optional<std::string> s_cos_argstr{};
inline static std::unordered_map<fs::path, std::wstring> s_cos_mounts{};
inline static bool s_enable_gdbstub = false;
inline static bool s_nsight_mode = false;
@ -59,5 +69,3 @@ private:
static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path);
};

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()
@ -548,8 +550,7 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false;
}
else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::MENU ||
initiatedBy == wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE)
else
{
wxString t = _("Unable to launch game\nPath:\n");
t.append(_pathToUtf8(launchPath));
@ -566,13 +567,6 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false;
}
else
{
wxString t = _("Unable to launch game\nPath:\n");
t.append(_pathToUtf8(launchPath));
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false;
}
}
if(launchTitle.IsValid())
@ -2483,6 +2477,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();