From 7854fb6bff27bec5317106c45583ad795794709c Mon Sep 17 00:00:00 2001 From: Phi1ow <331962174+Phi1ow@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:33:43 +0200 Subject: [PATCH 1/2] feat(errordialog): implement the libSceErrorDialog state machine The six libSceErrorDialog exports were throwing stubs. They now implement the dialog's NONE -> INITIALIZED -> RUNNING -> FINISHED state machine without a visible window, so a title that opens an error dialog and waits for it never blocks: - sceErrorDialogInitialize -> INITIALIZED, or 0x80ED0002 if already initialized - sceErrorDialogOpen -> checks the state and the 16-byte parameter, then RUNNING - sceErrorDialogUpdateStatus -> turns RUNNING into FINISHED and returns the status - sceErrorDialogGetStatus -> returns the status - sceErrorDialogClose -> FINISHED, or 0x80ED0005 when no dialog is running - sceErrorDialogTerminate -> NONE, or 0x80ED0001 when not initialized Re-opening is allowed once a previous dialog has finished. The status values and error codes are the documented SCE error-dialog ones. Adds a CMakeLists.txt so the library can carry its guest test. New test core/libs/tests/GuestErrorDialog.cpp drives the whole state machine (the not-initialized and already-initialized errors, the state and parameter checks of open, open/update/close and re-open after finish). It fails on main (the stubs throw) and passes with the change. --- .../libs/prx/libSceErrorDialog/CMakeLists.txt | 20 ++++ core/libs/prx/libSceErrorDialog/Export.cpp | 94 ++++++++++++++----- core/libs/tests/GuestErrorDialog.cpp | 63 +++++++++++++ 3 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 core/libs/prx/libSceErrorDialog/CMakeLists.txt create mode 100644 core/libs/tests/GuestErrorDialog.cpp diff --git a/core/libs/prx/libSceErrorDialog/CMakeLists.txt b/core/libs/prx/libSceErrorDialog/CMakeLists.txt new file mode 100644 index 000000000..2dd29a15a --- /dev/null +++ b/core/libs/prx/libSceErrorDialog/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceErrorDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceErrorDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceErrorDialog) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_error_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestErrorDialog.cpp) + target_include_directories(guest_error_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_error_dialog_tests PRIVATE libSceErrorDialog) + add_test(NAME guest_error_dialog COMMAND guest_error_dialog_tests) + if(WIN32) + set_tests_properties(guest_error_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceErrorDialog/Export.cpp b/core/libs/prx/libSceErrorDialog/Export.cpp index 7588126f3..46b7777ae 100644 --- a/core/libs/prx/libSceErrorDialog/Export.cpp +++ b/core/libs/prx/libSceErrorDialog/Export.cpp @@ -1,39 +1,89 @@ #include #include +#include +#include + #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#ifdef _WIN32 +#include +#endif + +namespace { +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusRunning = 2; +constexpr int kStatusFinished = 3; + +constexpr int kErrOk = 0; +constexpr int kErrNotInitialized = static_cast(0x80ED0001u); +constexpr int kErrAlreadyInitialized = static_cast(0x80ED0002u); +constexpr int kErrParam = static_cast(0x80ED0003u); +constexpr int kErrInvalidState = static_cast(0x80ED0005u); + +constexpr std::size_t kParamSize = 16; + +std::mutex g_lock; +int g_status = kStatusNone; + +bool Readable(const void* p, std::size_t n) { +#ifdef _WIN32 + if (p == nullptr) return false; + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0 || mbi.State != MEM_COMMIT) return false; + if ((mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) return false; + const auto end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + return reinterpret_cast(p) + n <= end; +#else + return p != nullptr; +#endif +} +} // namespace + extern "C" { -int APS5_VABI sceErrorDialogClose(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceErrorDialogGetStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceErrorDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + if (g_status != kStatusNone) return kErrAlreadyInitialized; + g_status = kStatusInitialized; + return kErrOk; } int APS5_VABI sceErrorDialogOpen(const void* param) { - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceErrorDialogTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + if (g_status != kStatusInitialized && g_status != kStatusFinished) return kErrInvalidState; + if (!Readable(param, kParamSize)) return kErrParam; + std::int32_t size = 0; + std::memcpy(&size, param, sizeof(size)); + if (static_cast(size) != kParamSize) return kErrParam; + g_status = kStatusRunning; + return kErrOk; } int APS5_VABI sceErrorDialogUpdateStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + if (g_status == kStatusRunning) g_status = kStatusFinished; + return g_status; +} + +int APS5_VABI sceErrorDialogGetStatus(void) { + std::lock_guard g(g_lock); + return g_status; +} + +int APS5_VABI sceErrorDialogClose(void) { + std::lock_guard g(g_lock); + if (g_status != kStatusRunning) return kErrInvalidState; + g_status = kStatusFinished; + return kErrOk; +} + +int APS5_VABI sceErrorDialogTerminate(void) { + std::lock_guard g(g_lock); + if (g_status == kStatusNone) return kErrNotInitialized; + g_status = kStatusNone; + return kErrOk; } } diff --git a/core/libs/tests/GuestErrorDialog.cpp b/core/libs/tests/GuestErrorDialog.cpp new file mode 100644 index 000000000..cb91dc4fb --- /dev/null +++ b/core/libs/tests/GuestErrorDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceErrorDialogInitialize(void); +int APS5_VABI sceErrorDialogOpen(const void* param); +int APS5_VABI sceErrorDialogUpdateStatus(void); +int APS5_VABI sceErrorDialogGetStatus(void); +int APS5_VABI sceErrorDialogClose(void); +int APS5_VABI sceErrorDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusRunning = 2; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80ED0001); +constexpr int kErrAlreadyInitialized = static_cast(0x80ED0002); +constexpr int kErrParam = static_cast(0x80ED0003); +constexpr int kErrInvalidState = static_cast(0x80ED0005); + +struct ErrorDialogParam { + std::int32_t size; + std::int32_t error_code; + std::int32_t user_id; + std::int32_t reserved; +}; + +} + +int main() { + ErrorDialogParam param{16, static_cast(0x80020001), 0, 0}; + + Require(sceErrorDialogTerminate() == kErrNotInitialized); + Require(sceErrorDialogOpen(¶m) == kErrInvalidState); + + Require(sceErrorDialogInitialize() == 0); + Require(sceErrorDialogInitialize() == kErrAlreadyInitialized); + + Require(sceErrorDialogClose() == kErrInvalidState); + + Require(sceErrorDialogOpen(nullptr) == kErrParam); + ErrorDialogParam bad{8, 0, 0, 0}; + Require(sceErrorDialogOpen(&bad) == kErrParam); + + Require(sceErrorDialogOpen(¶m) == 0); + Require(sceErrorDialogGetStatus() == kStatusRunning); + Require(sceErrorDialogOpen(¶m) == kErrInvalidState); + + Require(sceErrorDialogUpdateStatus() == kStatusFinished); + Require(sceErrorDialogGetStatus() == kStatusFinished); + + Require(sceErrorDialogOpen(¶m) == 0); + Require(sceErrorDialogGetStatus() == kStatusRunning); + Require(sceErrorDialogClose() == 0); + Require(sceErrorDialogGetStatus() == kStatusFinished); + + Require(sceErrorDialogTerminate() == 0); + Require(sceErrorDialogTerminate() == kErrNotInitialized); +} From 6571ace5bda2a092c7ce38bf1eaebd1fb132ab63 Mon Sep 17 00:00:00 2001 From: Phi1ow <331962174+Phi1ow@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:32:13 +0200 Subject: [PATCH 2/2] feat(errordialog): log the error code and record the silent stub Adds the dialog to the silent-stubs list in docs/dev/TechnicalDebt.md, since it finishes without showing anything. sceErrorDialogOpen now logs the error code and user id from the parameter (offsets 4 and 8), like libSceMsgDialog logs its message, so an error a title would have shown the player stays visible instead of disappearing. --- core/libs/prx/libSceErrorDialog/Export.cpp | 6 ++++++ docs/dev/TechnicalDebt.md | 1 + 2 files changed, 7 insertions(+) diff --git a/core/libs/prx/libSceErrorDialog/Export.cpp b/core/libs/prx/libSceErrorDialog/Export.cpp index 46b7777ae..17d76ba8e 100644 --- a/core/libs/prx/libSceErrorDialog/Export.cpp +++ b/core/libs/prx/libSceErrorDialog/Export.cpp @@ -4,6 +4,7 @@ #include #include "SceTypes.hpp" +#include "HitLog.hpp" #include "prx/libc/include/General.hpp" #ifdef _WIN32 @@ -57,6 +58,11 @@ int APS5_VABI sceErrorDialogOpen(const void* param) { std::int32_t size = 0; std::memcpy(&size, param, sizeof(size)); if (static_cast(size) != kParamSize) return kErrParam; + std::int32_t errorCode = 0; + std::int32_t userId = 0; + std::memcpy(&errorCode, static_cast(param) + 4, sizeof(errorCode)); + std::memcpy(&userId, static_cast(param) + 8, sizeof(userId)); + APS5_HIT("ERRORDIALOG", "open: error_code=0x%08X user_id=%d", errorCode, userId); g_status = kStatusRunning; return kErrOk; } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a66170e1c..b3c9bf731 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -13,6 +13,7 @@ Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: - [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) - [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp) +- [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. - [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling