fix(savedata): keep progress dialogs running until closed
Opening a save progress dialog immediately reported FINISHED. Skyrim waited for RUNNING before writing SAVEDATA.DAT and never resumed. Keep progress-bar mode running through polling and progress updates until Close, retaining the existing automatic responses for other dialog modes. Add lifecycle regression coverage for repeated polling, completion, reopening, result metadata and ordinary selection dialogs. The test failed on the previous implementation and passes with the fix on Fedora 44 Release. Correcting the live dialog state recovered a blocked Skyrim save without restarting its session. Written with AI assistance.
This commit is contained in:
parent
df16c4c256
commit
97f16be342
|
|
@ -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:$<TARGET_FILE_DIR:libSceSaveDataDialog.native>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ constexpr int SAVE_DATA_DIALOG_ERROR_ARG_NULL = static_cast<int>(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;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
#include "prx/libSceSaveDataDialog.native/SaveDataDialog.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
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");
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue