diff --git a/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt b/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt new file mode 100644 index 0000000000..02e53fdc16 --- /dev/null +++ b/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libScePlayerSelectionDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePlayerSelectionDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePlayerSelectionDialog) + +if(BUILD_TESTING) + add_test_executable(guest_player_selection_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestPlayerSelectionDialog.cpp) + target_include_directories(guest_player_selection_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_player_selection_dialog_tests PRIVATE libScePlayerSelectionDialog) + add_test(NAME guest_player_selection_dialog COMMAND guest_player_selection_dialog_tests) + if(WIN32) + set_tests_properties(guest_player_selection_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp index e32913a7ad..6093f69d67 100644 --- a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp +++ b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp @@ -1,11 +1,60 @@ +#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; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { +int APS5_VABI scePlayerSelectionDialogInitialize(void) { + 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 scePlayerSelectionDialogOpen(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 scePlayerSelectionDialogUpdateStatus(void) { + return g_status.load(); +} + +int APS5_VABI scePlayerSelectionDialogGetResult(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); + return 0; +} + int APS5_VABI scePlayerSelectionDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/tests/GuestPlayerSelectionDialog.cpp b/core/libs/tests/GuestPlayerSelectionDialog.cpp new file mode 100644 index 0000000000..cd5d62693e --- /dev/null +++ b/core/libs/tests/GuestPlayerSelectionDialog.cpp @@ -0,0 +1,50 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI scePlayerSelectionDialogInitialize(void); +int APS5_VABI scePlayerSelectionDialogOpen(const void* param); +int APS5_VABI scePlayerSelectionDialogUpdateStatus(void); +int APS5_VABI scePlayerSelectionDialogGetResult(void* result); +int APS5_VABI scePlayerSelectionDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +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; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(scePlayerSelectionDialogTerminate() == kErrNotInitialized); + Require(scePlayerSelectionDialogOpen(¶m) == kErrNotInitialized); + Require(scePlayerSelectionDialogGetResult(result) == kErrNotInitialized); + + Require(scePlayerSelectionDialogInitialize() == 0); + Require(scePlayerSelectionDialogInitialize() == kErrAlreadyInitialized); + + Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); + Require(scePlayerSelectionDialogGetResult(result) == kErrNotFinished); + + Require(scePlayerSelectionDialogOpen(¶m) == 0); + Require(scePlayerSelectionDialogUpdateStatus() == kStatusFinished); + + Require(scePlayerSelectionDialogGetResult(nullptr) == kErrArgNull); + Require(scePlayerSelectionDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(scePlayerSelectionDialogTerminate() == 0); + Require(scePlayerSelectionDialogTerminate() == kErrNotInitialized); +} diff --git a/docs/dev/debt/libScePlayerSelectionDialog.md b/docs/dev/debt/libScePlayerSelectionDialog.md new file mode 100644 index 0000000000..602e740063 --- /dev/null +++ b/docs/dev/debt/libScePlayerSelectionDialog.md @@ -0,0 +1,5 @@ +# libScePlayerSelectionDialog technical debt + +### Silent stubs + +- [libScePlayerSelectionDialog](../../../core/libs/prx/libScePlayerSelectionDialog/Export.cpp) - the player-selection dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. `GetResult` writes only the first `int32` (`USER_CANCELED`); the rest of `ScePlayerSelectionDialogResult` (player list pointer and length) is not filled. EA SPORTS FC 27 (PPSA34015) fails to load `libScePlayerSelectionDialog.prx` without `Initialize`, `Open`, `UpdateStatus` and `GetResult` (`unresolved ELF import` / `GetLastError: 127`)