diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index d094f39cf..7742a908e 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -206,6 +206,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_netctl_ap_dialog_tests tests/GuestNetCtlApDialog.cpp) + target_include_directories(guest_netctl_ap_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_netctl_ap_dialog_tests PRIVATE libSceNetCtlApDialog libc) + configure_windows_unwind(guest_netctl_ap_dialog_tests) + 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() + add_test_executable(guest_rudp_status_tests tests/GuestRudpStatus.cpp) target_include_directories(guest_rudp_status_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_rudp_status_tests PRIVATE libSceRudp libc) diff --git a/core/libs/prx/libSceNetCtlApDialog/Export.cpp b/core/libs/prx/libSceNetCtlApDialog/Export.cpp index 0dc6cd822..e1d2bc4a2 100644 --- a/core/libs/prx/libSceNetCtlApDialog/Export.cpp +++ b/core/libs/prx/libSceNetCtlApDialog/Export.cpp @@ -1,38 +1,78 @@ +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +struct SceNetCtlApDialogResult { + std::int32_t result = COMMON_DIALOG_RESULT_USER_CANCELED; + std::uint8_t reserved[36] = {}; +}; + +static_assert(sizeof(SceNetCtlApDialogResult) == 40); + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { int APS5_VABI sceNetCtlApDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + return 0; } int APS5_VABI sceNetCtlApDialogTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + return 0; } -int APS5_VABI sceNetCtlApDialogOpen(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetCtlApDialogOpen(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; } int APS5_VABI sceNetCtlApDialogClose(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; } int APS5_VABI sceNetCtlApDialogUpdateStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return g_status.load(); } -int APS5_VABI sceNetCtlApDialogGetResult(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetCtlApDialogGetStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceNetCtlApDialogGetResult(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + const SceNetCtlApDialogResult dialogResult{}; + std::memcpy(result, &dialogResult, sizeof(dialogResult)); + return 0; } } diff --git a/core/libs/tests/GuestNetCtlApDialog.cpp b/core/libs/tests/GuestNetCtlApDialog.cpp new file mode 100644 index 000000000..bdce6ca10 --- /dev/null +++ b/core/libs/tests/GuestNetCtlApDialog.cpp @@ -0,0 +1,81 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI sceNetCtlApDialogInitialize(void); +int APS5_VABI sceNetCtlApDialogTerminate(void); +int APS5_VABI sceNetCtlApDialogOpen(const void* param); +int APS5_VABI sceNetCtlApDialogClose(void); +int APS5_VABI sceNetCtlApDialogUpdateStatus(void); +int APS5_VABI sceNetCtlApDialogGetStatus(void); +int APS5_VABI sceNetCtlApDialogGetResult(void* result); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +void RequireCanceled(const std::uint8_t (&result)[44]) { + std::int32_t code = 0; + std::memcpy(&code, result, sizeof(code)); + Require(code == kResultUserCanceled); + for (int i = 4; i < 40; ++i) Require(result[i] == 0); + for (int i = 40; i < 44; ++i) Require(result[i] == 0x5a); +} + +} + +int main() { + std::uint8_t param[104] = {}; + std::uint8_t result[44]; + std::memset(result, 0x5a, sizeof(result)); + + Require(sceNetCtlApDialogTerminate() == kErrNotInitialized); + Require(sceNetCtlApDialogOpen(param) == kErrNotInitialized); + Require(sceNetCtlApDialogClose() == kErrNotInitialized); + Require(sceNetCtlApDialogGetResult(result) == kErrNotInitialized); + Require(sceNetCtlApDialogGetStatus() == kStatusNone); + Require(sceNetCtlApDialogUpdateStatus() == kStatusNone); + + Require(sceNetCtlApDialogInitialize() == 0); + Require(sceNetCtlApDialogInitialize() == kErrAlreadyInitialized); + Require(sceNetCtlApDialogGetStatus() == kStatusInitialized); + Require(sceNetCtlApDialogUpdateStatus() == kStatusInitialized); + + Require(sceNetCtlApDialogOpen(nullptr) == kErrArgNull); + Require(sceNetCtlApDialogGetStatus() == kStatusInitialized); + Require(sceNetCtlApDialogGetResult(result) == kErrNotFinished); + + Require(sceNetCtlApDialogOpen(param) == 0); + Require(sceNetCtlApDialogGetStatus() == kStatusFinished); + Require(sceNetCtlApDialogUpdateStatus() == kStatusFinished); + + Require(sceNetCtlApDialogGetResult(nullptr) == kErrArgNull); + Require(sceNetCtlApDialogGetResult(result) == 0); + RequireCanceled(result); + + Require(sceNetCtlApDialogOpen(param) == 0); + Require(sceNetCtlApDialogClose() == 0); + Require(sceNetCtlApDialogTerminate() == 0); + Require(sceNetCtlApDialogTerminate() == kErrNotInitialized); + Require(sceNetCtlApDialogGetStatus() == kStatusNone); + + Require(sceNetCtlApDialogInitialize() == 0); + Require(sceNetCtlApDialogClose() == 0); + Require(sceNetCtlApDialogGetStatus() == kStatusFinished); + std::memset(result, 0x5a, sizeof(result)); + Require(sceNetCtlApDialogGetResult(result) == 0); + RequireCanceled(result); + Require(sceNetCtlApDialogTerminate() == 0); +} diff --git a/docs/dev/debt/libSceNetCtlApDialog.md b/docs/dev/debt/libSceNetCtlApDialog.md new file mode 100644 index 000000000..aefc80069 --- /dev/null +++ b/docs/dev/debt/libSceNetCtlApDialog.md @@ -0,0 +1,5 @@ +# libSceNetCtlApDialog technical debt + +### Silent stubs + +- [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, as no access-point connection is brought up. `Open` takes the 104-byte parameter block and `GetResult` writes a 40-byte result (int32 result and 36 zeroed reserved bytes), following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/NetCtlApDialog.cs); the parameter block is not validated and the 0x80B8xxxx error codes follow the other common dialogs, neither verified on hardware. Deathloop imports it