diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 00000000..d43d8198 --- /dev/null +++ b/.gdbinit @@ -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) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 79184393..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; @@ -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 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/Filesystem/FST/FST.cpp b/src/Cafe/Filesystem/FST/FST.cpp index ec112b9a..1652b1fe 100644 --- a/src/Cafe/Filesystem/FST/FST.cpp +++ b/src/Cafe/Filesystem/FST/FST.cpp @@ -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 keyFile(FileStream::openFile2(keyPath)); + return keyFile && keyFile->readData(discTitleKey.b, sizeof(discTitleKey.b)) == sizeof(discTitleKey.b); } // open WUD image using key cache 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 13ef6613..bae0dca8 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -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 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* 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); } }; diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index 4ac12c53..2d4a5316 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -63,7 +63,7 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) #endif ("game,g", po::wvalue(), "Path of game to launch") - ("title-id,t", po::value(), "Title ID of the title to be launched (overridden by --game)") + ("title-id,t", po::value(), "Title ID of the title to be launched (overridden by --game)") ("mlc,m", po::wvalue(), "Custom mlc folder location") ("fullscreen,f", po::value()->implicit_value(true), "Launch games in fullscreen mode") @@ -71,6 +71,10 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) ("account,a", po::value(), "Persistent id of account") + ("cos-mounts", po::wvalue>()->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(), "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()->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()->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()->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& args) if (vm.count("enable-gdbstub")) s_enable_gdbstub = vm["enable-gdbstub"].as(); + if (vm.count("forward-console-logging")) + { + requireConsole(); + s_forward_console_logging = true; + } + + if (vm.count("cos-argstr")) + s_cos_argstr = vm["cos-argstr"].as(); + + if (vm.count("cos-mounts")) + { + for (const auto& argument : vm["cos-mounts"].as>()) + { + 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")) diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index 13665cb7..df52a84b 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -2,6 +2,7 @@ #include #include +#include class LaunchSettings { @@ -16,7 +17,7 @@ public: static bool HandleCommandline(const std::vector& args); static std::optional GetLoadFile() { return s_load_game_file; } - static std::optional GetLoadTitleID() {return s_load_title_id;} + static std::optional GetLoadTitleID() {return s_load_title_id;} static std::optional GetMLCPath() { return s_mlc_path; } static std::optional 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 CosArgstr() { return s_cos_argstr; } + static void ClearCosArgstr() { s_cos_argstr.reset(); } + static std::unordered_map& 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 s_load_game_file{}; - inline static std::optional s_load_title_id{}; + inline static std::optional s_load_title_id{}; inline static std::optional s_mlc_path{}; inline static std::optional s_render_upside_down{}; inline static std::optional s_fullscreen{}; inline static bool s_verbose = false; - + + inline static bool s_forward_console_logging = false; + inline static std::optional s_cos_argstr{}; + inline static std::unordered_map 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); }; - - 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 335eeddd..c0dd8724 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() @@ -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); 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();