diff --git a/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt b/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt new file mode 100644 index 000000000..ddbc456db --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceSaveDataDialog.native SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceSaveDataDialog.native PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceSaveDataDialog.native) + +if(BUILD_TESTING) + add_test_executable(guest_savedata_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataDialog.cpp) + target_include_directories(guest_savedata_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_savedata_dialog_tests PRIVATE libSceSaveDataDialog.native) + configure_windows_unwind(guest_savedata_dialog_tests) + add_test(NAME guest_savedata_dialog COMMAND guest_savedata_dialog_tests) + if(WIN32) + set_tests_properties(guest_savedata_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceSaveDataDialog.native/Export.cpp b/core/libs/prx/libSceSaveDataDialog.native/Export.cpp index 0835cdff4..3c834f7d9 100644 --- a/core/libs/prx/libSceSaveDataDialog.native/Export.cpp +++ b/core/libs/prx/libSceSaveDataDialog.native/Export.cpp @@ -69,7 +69,8 @@ int APS5_VABI sceSaveDataDialogOpen(const void* param) { } } } - g_status = SAVE_DATA_DIALOG_STATUS_FINISHED; + g_status = g_mode == SAVE_DATA_DIALOG_MODE_PROGRESS_BAR + ? SAVE_DATA_DIALOG_STATUS_RUNNING : SAVE_DATA_DIALOG_STATUS_FINISHED; return SAVE_DATA_DIALOG_OK; } diff --git a/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialog.hpp b/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialog.hpp index 907db674a..3259b8d48 100644 --- a/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialog.hpp +++ b/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialog.hpp @@ -12,8 +12,11 @@ constexpr int SAVE_DATA_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000D); constexpr int SAVE_DATA_DIALOG_STATUS_NONE = 0; constexpr int SAVE_DATA_DIALOG_STATUS_INITIALIZED = 1; +constexpr int SAVE_DATA_DIALOG_STATUS_RUNNING = 2; constexpr int SAVE_DATA_DIALOG_STATUS_FINISHED = 3; +constexpr int SAVE_DATA_DIALOG_MODE_PROGRESS_BAR = 5; + constexpr int SAVE_DATA_DIALOG_RESULT_OK = 0; constexpr int SAVE_DATA_DIALOG_BUTTON_ID_OK = 1; diff --git a/core/libs/tests/GuestSaveDataDialog.cpp b/core/libs/tests/GuestSaveDataDialog.cpp new file mode 100644 index 000000000..5d8e06371 --- /dev/null +++ b/core/libs/tests/GuestSaveDataDialog.cpp @@ -0,0 +1,78 @@ +#include "prx/libSceSaveDataDialog.native/SaveDataDialog.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceSaveDataDialogInitialize(); +int APS5_VABI sceSaveDataDialogOpen(const void* param); +int APS5_VABI sceSaveDataDialogGetStatus(); +int APS5_VABI sceSaveDataDialogUpdateStatus(); +int APS5_VABI sceSaveDataDialogIsReadyToDisplay(); +int APS5_VABI sceSaveDataDialogProgressBarInc(int target, std::uint32_t delta); +int APS5_VABI sceSaveDataDialogProgressBarSetValue(int target, std::uint32_t rate); +int APS5_VABI sceSaveDataDialogClose(const void* param); +int APS5_VABI sceSaveDataDialogGetResult(void* result); +int APS5_VABI sceSaveDataDialogTerminate(); +} + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static void RequireRunning() { + Require(sceSaveDataDialogGetStatus() == SAVE_DATA_DIALOG_STATUS_RUNNING, + "save progress must remain running until closed"); + Require(sceSaveDataDialogUpdateStatus() == SAVE_DATA_DIALOG_STATUS_RUNNING, + "polling save progress must not finish the dialog"); + Require(sceSaveDataDialogIsReadyToDisplay() == 1, "save progress must be ready"); +} + +int main() { + Require(sceSaveDataDialogInitialize() == 0, "initialize"); + + SaveDataDirName names[2]{{}, {"SDU1"}}; + SaveDataDialogItems items{}; + items.dir_names = names; + items.dir_names_num = 2; + int context = 42; + SaveDataDialogParam param{}; + param.size = sizeof(param); + param.mode = SAVE_DATA_DIALOG_MODE_PROGRESS_BAR; + param.items = &items; + param.user_data = &context; + + for (int save = 0; save < 2; ++save) { + Require(sceSaveDataDialogOpen(¶m) == 0, "open save progress"); + for (int poll = 0; poll < 8; ++poll) RequireRunning(); + Require(sceSaveDataDialogOpen(¶m) == SAVE_DATA_DIALOG_ERROR_INVALID_STATE, + "opening another dialog must not interrupt an active save"); + Require(sceSaveDataDialogProgressBarSetValue(0, 25) == 0, "set progress"); + Require(sceSaveDataDialogProgressBarInc(0, 25) == 0, "increment progress"); + RequireRunning(); + Require(sceSaveDataDialogProgressBarSetValue(0, 100) == 0, "complete progress"); + RequireRunning(); + Require(sceSaveDataDialogClose(nullptr) == 0, "close save progress"); + Require(sceSaveDataDialogUpdateStatus() == SAVE_DATA_DIALOG_STATUS_FINISHED, + "closing save progress must finish it"); + SaveDataDirName selected{}; + SaveDataDialogResult result{}; + result.dir_name = &selected; + Require(sceSaveDataDialogGetResult(&result) == 0, "read completed result"); + Require(result.mode == param.mode && result.result == SAVE_DATA_DIALOG_RESULT_OK && + result.user_data == &context && std::strcmp(selected.data, "SDU1") == 0, + "save result must preserve its mode, directory and caller context"); + } + + param.mode = 1; + Require(sceSaveDataDialogOpen(¶m) == 0, "open selection dialog"); + Require(sceSaveDataDialogUpdateStatus() == SAVE_DATA_DIALOG_STATUS_FINISHED, + "selection dialog must retain the existing automatic response"); + Require(sceSaveDataDialogTerminate() == 0, "terminate"); + Require(sceSaveDataDialogGetStatus() == SAVE_DATA_DIALOG_STATUS_NONE, "terminated state"); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 084451064..4fd66b20b 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -11,7 +11,7 @@ ### Silent stubs 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) +- [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) shows no UI and ignores progress values. Progress-bar mode (5) stays running until the caller closes it, matching Skyrim PPSA03747's save sequence; other modes still finish immediately and return button 1 and the first nonempty directory name. Console dialog timing and selection behavior remain unverified. - [libSceMsgDialog.native](../../core/libs/prx/libSceMsgDialog.native/Export.cpp) - dialogs finish at `sceMsgDialogOpen` without UI and answer with button 1, so `sceMsgDialogClose` returns `NOT_RUNNING`; its result before `sceMsgDialogInitialize` follows libSceMsgDialog - [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