From 2920c7f04cbc1b05ffa8c3e18e4b22c384e2a95c Mon Sep 17 00:00:00 2001 From: SamG-Coder <13449963+SamG-Coder@users.noreply.github.com> Date: Fri, 25 Sep 2026 07:02:37 +1000 Subject: [PATCH] feat(system): handle console exit through runtime shutdown --- core/libs/CMakeLists.txt | 12 ++++++++ core/libs/prx/libSceSystemService/Export.cpp | 12 ++++++++ core/libs/tests/GuestSystemExit.cpp | 30 ++++++++++++++++++++ docs/YAMAGI_QUAKE2.md | 17 +++++++++++ 4 files changed, 71 insertions(+) create mode 100644 core/libs/tests/GuestSystemExit.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index f00f78d4e..c7b0e51ab 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -104,6 +104,18 @@ endforeach() add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS}) if(BUILD_TESTING) + add_executable(guest_system_exit_tests tests/GuestSystemExit.cpp) + target_include_directories(guest_system_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_system_exit_tests PRIVATE libSceSystemService libc) + configure_windows_unwind(guest_system_exit_tests) + add_test(NAME guest_system_exit_parameters COMMAND guest_system_exit_tests) + add_test(NAME guest_system_exit COMMAND guest_system_exit_tests --exit) + set_tests_properties(guest_system_exit PROPERTIES + PASS_REGULAR_EXPRESSION "Guest shutdown and atexit completed") + if(WIN32) + set_tests_properties(guest_system_exit guest_system_exit_parameters PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_executable(guest_character_tests tests/GuestCharacter.cpp) target_include_directories(guest_character_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_character_tests PRIVATE libc) diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 3756dc36f..a9274a3cb 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -1,12 +1,24 @@ #include #include #include +#include +#include "prx/libc/include/Shutdown.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceSystemService/SystemService.hpp" extern "C" { +int APS5_VABI sceSystemServiceLoadExec(const char* path, const char* const* arguments) { + if (!path || !*path) return SYSTEM_SERVICE_ERROR_PARAMETER; + if (std::strcmp(path, "exit") != 0) { + NotImplemented_nid_no_patch("sceSystemServiceLoadExec: executable replacement"); + } + (void)arguments; + LibcRunShutdown_nid_postfix(); + std::exit(0); +} + int APS5_VABI sceSystemServiceDisableNoticeScreenSkipFlagAutoSet(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestSystemExit.cpp b/core/libs/tests/GuestSystemExit.cpp new file mode 100644 index 000000000..fc83c47eb --- /dev/null +++ b/core/libs/tests/GuestSystemExit.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libSceSystemService/SystemService.hpp" +#include +#include +#include +extern "C" int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); +namespace { +bool cleaned = false; +void Cleanup() { cleaned = true; } +void VerifyExit() { + if (!cleaned) std::_Exit(1); + std::puts("Guest shutdown and atexit completed"); +} +void Require(bool value) { if (!value) std::abort(); } +} +int main(int argc, char**) { + if (argc > 1) { + LibcRegisterShutdown_nid_postfix(Cleanup); + Require(std::atexit(VerifyExit) == 0); + sceSystemServiceLoadExec("exit", nullptr); + return 2; + } + Require(sceSystemServiceLoadExec(nullptr, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(sceSystemServiceLoadExec("", nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + bool rejected = false; + try { sceSystemServiceLoadExec("/app0/another.bin", nullptr); } + catch (const std::runtime_error&) { rejected = true; } + Require(rejected); +} diff --git a/docs/YAMAGI_QUAKE2.md b/docs/YAMAGI_QUAKE2.md index dcdc3e9a5..6be6905af 100644 --- a/docs/YAMAGI_QUAKE2.md +++ b/docs/YAMAGI_QUAKE2.md @@ -391,3 +391,20 @@ Latest unchanged-game startup resolves ftello and stops at `JoBqSQt1yyA` shows it requests the console's `exit` operation from its quit path. This is source evidence only; the guest entry point still has not executed and no frame has been rendered. + +## Follow-up: console exit request + +Implemented sceSystemServiceLoadExec for the `exit` operation through the shared +runtime shutdown callbacks followed by normal process exit. Null/empty paths +return the existing system-service parameter error. Replacing the process with +another executable remains explicitly unsupported and raises the runtime's +existing not-implemented diagnostic; it does not report a successful launch. + +An isolated CTest process verifies that shutdown callbacks execute before +atexit handlers and that the service does not return after requesting exit. +A separate test checks invalid arguments and executable-replacement rejection. +All twenty-three tests pass and libraries rebuild. + +The unchanged game resolves sceSystemServiceLoadExec and now stops at +`gkWgn0p1AfU` (`freopen`). This does not establish that the game has executed +its quit path: guest entry-point execution and rendered output remain unverified.