From d2d997a04d8c923cd5e51f77a3af6a1241432b41 Mon Sep 17 00:00:00 2001 From: Semir Kabir Date: Sun, 11 Oct 2026 19:19:34 +0000 Subject: [PATCH] feat(libSceNetCtlApDialog): run the common-dialog state machine The access-point dialog shows nothing and reports the user as having cancelled, following the libSceErrorDialog and libSceLoginDialog pattern. Adds the seventh export sceNetCtlApDialogGetStatus (NID eBWS-HorSWY), whose signature is assumed from the other common dialogs, a guest test, and a TechnicalDebt entry covering the assumed signatures and error codes. --- .../prx/libSceNetCtlApDialog/CMakeLists.txt | 14 +++++ core/libs/prx/libSceNetCtlApDialog/Export.cpp | 56 +++++++++++++------ core/libs/tests/GuestNetCtlApDialog.cpp | 48 ++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 core/libs/prx/libSceNetCtlApDialog/CMakeLists.txt create mode 100644 core/libs/tests/GuestNetCtlApDialog.cpp diff --git a/core/libs/prx/libSceNetCtlApDialog/CMakeLists.txt b/core/libs/prx/libSceNetCtlApDialog/CMakeLists.txt new file mode 100644 index 00000000000..f5ccaebdddb --- /dev/null +++ b/core/libs/prx/libSceNetCtlApDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceNetCtlApDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNetCtlApDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNetCtlApDialog) + +if(BUILD_TESTING) + add_test_executable(guest_netctl_ap_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetCtlApDialog.cpp) + target_include_directories(guest_netctl_ap_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_netctl_ap_dialog_tests PRIVATE libSceNetCtlApDialog) + add_test(NAME guest_netctl_ap_dialog COMMAND guest_netctl_ap_dialog_tests) + if(WIN32) + set_tests_properties(guest_netctl_ap_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNetCtlApDialog/Export.cpp b/core/libs/prx/libSceNetCtlApDialog/Export.cpp index 0dc6cd822a7..f9257e6cfdd 100644 --- a/core/libs/prx/libSceNetCtlApDialog/Export.cpp +++ b/core/libs/prx/libSceNetCtlApDialog/Export.cpp @@ -1,38 +1,62 @@ +#include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -extern "C" { +namespace { + +constexpr int STATUS_NONE = 0; +constexpr int STATUS_INITIALIZED = 1; +constexpr int STATUS_RUNNING = 2; +constexpr int STATUS_FINISHED = 3; + +constexpr int ERROR_NOT_INITIALIZED = static_cast(0x80412101u); +constexpr int ERROR_BUSY = static_cast(0x8041210Cu); +constexpr int ERROR_INVALID_STATE = static_cast(0x8041210Eu); +constexpr int ERROR_USER_CANCELED = static_cast(0x80412110u); + +std::atomic g_status{STATUS_NONE}; -int APS5_VABI sceNetCtlApDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; } -int APS5_VABI sceNetCtlApDialogTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +extern "C" { + +int APS5_VABI sceNetCtlApDialogInitialize(void) { + int expected = STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, STATUS_INITIALIZED)) return ERROR_INVALID_STATE; + return 0; } int APS5_VABI sceNetCtlApDialogOpen(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + const int status = g_status.load(); + if (status == STATUS_NONE) return ERROR_NOT_INITIALIZED; + if (status == STATUS_RUNNING) return ERROR_BUSY; + g_status = STATUS_FINISHED; + return 0; } -int APS5_VABI sceNetCtlApDialogClose(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetCtlApDialogGetStatus(void) { + return g_status.load(); } int APS5_VABI sceNetCtlApDialogUpdateStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (g_status.load() == STATUS_NONE) return ERROR_NOT_INITIALIZED; + return g_status.load(); } int APS5_VABI sceNetCtlApDialogGetResult(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ERROR_USER_CANCELED; +} + +int APS5_VABI sceNetCtlApDialogClose(void) { + if (g_status.load() == STATUS_NONE) return ERROR_NOT_INITIALIZED; + return 0; +} + +int APS5_VABI sceNetCtlApDialogTerminate(void) { + if (g_status.exchange(STATUS_NONE) == STATUS_NONE) return ERROR_NOT_INITIALIZED; + return 0; } } diff --git a/core/libs/tests/GuestNetCtlApDialog.cpp b/core/libs/tests/GuestNetCtlApDialog.cpp new file mode 100644 index 00000000000..64965924e0a --- /dev/null +++ b/core/libs/tests/GuestNetCtlApDialog.cpp @@ -0,0 +1,48 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceNetCtlApDialogInitialize(void); +int APS5_VABI sceNetCtlApDialogOpen(void); +int APS5_VABI sceNetCtlApDialogGetStatus(void); +int APS5_VABI sceNetCtlApDialogUpdateStatus(void); +int APS5_VABI sceNetCtlApDialogGetResult(void); +int APS5_VABI sceNetCtlApDialogClose(void); +int APS5_VABI sceNetCtlApDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80412101); +constexpr int kErrInvalidState = static_cast(0x8041210E); +constexpr int kErrUserCanceled = static_cast(0x80412110); + +} + +int main() { + Require(sceNetCtlApDialogTerminate() == kErrNotInitialized); + Require(sceNetCtlApDialogOpen() == kErrNotInitialized); + Require(sceNetCtlApDialogUpdateStatus() == kErrNotInitialized); + + Require(sceNetCtlApDialogInitialize() == 0); + Require(sceNetCtlApDialogInitialize() == kErrInvalidState); + + Require(sceNetCtlApDialogGetStatus() == 1); + Require(sceNetCtlApDialogGetResult() == kErrUserCanceled); + + Require(sceNetCtlApDialogOpen() == 0); + Require(sceNetCtlApDialogGetStatus() == kStatusFinished); + Require(sceNetCtlApDialogUpdateStatus() == kStatusFinished); + + Require(sceNetCtlApDialogOpen() == 0); + Require(sceNetCtlApDialogGetStatus() == kStatusFinished); + + Require(sceNetCtlApDialogClose() == 0); + Require(sceNetCtlApDialogTerminate() == 0); + Require(sceNetCtlApDialogTerminate() == kErrNotInitialized); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7c8..7e7558da7b3 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -17,6 +17,7 @@ Throughout the project, every function at every stage either **does exactly what - [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 - [libSceLoginDialog](../../core/libs/prx/libSceLoginDialog/Export.cpp) - the login dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled +- [libSceNetCtlApDialog](../../core/libs/prx/libSceNetCtlApDialog/Export.cpp) - the access-point dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled: `sceNetCtlApDialogGetResult` returns the user-canceled error (0x80412110) without any result out-argument, whose existence on the console is unknown. All seven NIDs of the library are known from [shadPS4's aerolib](https://github.com/shadps4-emu/shadPS4/blob/main/scripts/aerolib.inl); the signatures (every function takes no argument) and the error codes (from libSceNetCtl's measured 0x804121xx range: NOT_INITIALIZED 0x80412101, BUSY 0x8041210C, INVALID_STATE 0x8041210E) are assumed, not verified on a PS5 title or hardware - [libSceCdlgG2P](../../core/libs/prx/libSceCdlgG2P/Export.cpp) - the G2P dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. The signatures (`Open` and `GetResult` take a pointer, the others none) and the leading `int32` result are assumed from the other common dialogs, not verified for G2P; PPSA01289 imports it - [libSceVrSetupDialog](../../core/libs/prx/libSceVrSetupDialog/Export.cpp) - the VR setup dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no headset is supported (see libSceHmd2). The signatures are assumed from the other common dialogs; PPSA12544 imports it - [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.