Implement player invitation dialog HLE stub

This commit is contained in:
Julien Legay 2026-09-28 09:53:27 +02:00 committed by boykopovar
parent 83358bec94
commit bd3e41b28f
6 changed files with 178 additions and 0 deletions

27
PR_DESCRIPTION.md Normal file
View File

@ -0,0 +1,27 @@
# Implement an HLE stub for libScePlayerInvitationDialog
## Why
Games that load `libScePlayerInvitationDialog` currently have no HLE implementation for the invitation dialog. This can prevent startup or leave games waiting indefinitely for the dialog to finish.
## Changes
- Add the PRX module and its `Initialize`, `Open`, `UpdateStatus`, `GetStatus`, `Close`, and `Terminate` exports.
- Simulate the `None -> Initialized -> Running -> Finished` lifecycle: the first `UpdateStatus` after `Open` finishes the dialog without displaying UI or starting a network operation.
- Handle repeated or out-of-order calls and protect shared state with a mutex.
- Log calls and available arguments to the console without dereferencing the opening parameters, whose ABI layout has not been confirmed.
- Add a focused test covering opening, polling, closing, reopening, and out-of-order calls.
## Verification
```text
cmake --build build_ninja --target player_invitation_dialog_tests
ctest --test-dir build_ninja -R "^player_invitation_dialog$" --output-on-failure
ninja -C build_ninja core/libs/libs/libScePlayerInvitationDialog.prx
```
The focused test passes, and the NID-patched PRX builds successfully.
## Limitations
This stub does not display system UI or send invitations. `ScePlayerInvitationDialogParam` is opaque; its exact layout and any additional exports still need to be confirmed against traces or a compatible SDK.

View File

@ -122,6 +122,15 @@ list(APPEND PATCHED_OUTPUTS ${LIBCOHTML_PROSPERO_PATCHED})
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
if(BUILD_TESTING)
add_executable(player_invitation_dialog_tests tests/PlayerInvitationDialog.cpp)
target_include_directories(player_invitation_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(player_invitation_dialog_tests PRIVATE libScePlayerInvitationDialog libc)
configure_windows_unwind(player_invitation_dialog_tests)
add_test(NAME player_invitation_dialog COMMAND player_invitation_dialog_tests)
if(WIN32)
set_tests_properties(player_invitation_dialog PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_library(host_thread_local_test_library SHARED tests/HostThreadLocalLibrary.cpp)
target_include_directories(host_thread_local_test_library PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
add_executable(host_thread_local_tests tests/HostThreadLocal.cpp)

View File

@ -0,0 +1,3 @@
add_library(libScePlayerInvitationDialog SHARED EXCLUDE_FROM_ALL libScePlayerInvitationDialog.cpp)
target_include_directories(libScePlayerInvitationDialog PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libScePlayerInvitationDialog)

View File

@ -0,0 +1,69 @@
#include "prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.h"
#include <mutex>
#include "prx/libc/include/general/LogMacros.hpp"
namespace {
std::mutex g_dialog_mutex;
PlayerInvitationDialogStatus g_dialog_status = PlayerInvitationDialogStatus::None;
std::int32_t status() {
return static_cast<std::int32_t>(g_dialog_status);
}
}
extern "C" {
std::int32_t APS5_VABI scePlayerInvitationDialogInitialize(void) {
std::lock_guard lock(g_dialog_mutex);
APS5_LOG_OUT("status=%d", status());
if (g_dialog_status == PlayerInvitationDialogStatus::None) {
g_dialog_status = PlayerInvitationDialogStatus::Initialized;
}
return 0;
}
std::int32_t APS5_VABI scePlayerInvitationDialogOpen(const ScePlayerInvitationDialogParam* param) {
std::lock_guard lock(g_dialog_mutex);
APS5_LOG_OUT("param=%p status=%d", static_cast<const void*>(param), status());
if (g_dialog_status == PlayerInvitationDialogStatus::None) {
g_dialog_status = PlayerInvitationDialogStatus::Initialized;
}
g_dialog_status = PlayerInvitationDialogStatus::Running;
return 0;
}
std::int32_t APS5_VABI scePlayerInvitationDialogUpdateStatus(void) {
std::lock_guard lock(g_dialog_mutex);
APS5_LOG_OUT("status=%d", status());
if (g_dialog_status == PlayerInvitationDialogStatus::Running) {
g_dialog_status = PlayerInvitationDialogStatus::Finished;
}
return status();
}
std::int32_t APS5_VABI scePlayerInvitationDialogGetStatus(void) {
std::lock_guard lock(g_dialog_mutex);
APS5_LOG_OUT("status=%d", status());
return status();
}
std::int32_t APS5_VABI scePlayerInvitationDialogClose(void) {
std::lock_guard lock(g_dialog_mutex);
APS5_LOG_OUT("status=%d", status());
if (g_dialog_status == PlayerInvitationDialogStatus::Running) {
g_dialog_status = PlayerInvitationDialogStatus::Finished;
}
return 0;
}
std::int32_t APS5_VABI scePlayerInvitationDialogTerminate(void) {
std::lock_guard lock(g_dialog_mutex);
APS5_LOG_OUT("status=%d", status());
g_dialog_status = PlayerInvitationDialogStatus::None;
return 0;
}
}

View File

@ -0,0 +1,30 @@
#ifndef CORE_LIBS_PRX_LIBSCEPLAYERINVITATIONDIALOG_LIBSCEPLAYERINVITATIONDIALOG_H
#define CORE_LIBS_PRX_LIBSCEPLAYERINVITATIONDIALOG_LIBSCEPLAYERINVITATIONDIALOG_H
#include <cstdint>
#include "SceTypes.hpp"
enum class PlayerInvitationDialogStatus : std::int32_t {
None = 0,
Initialized = 1,
Running = 2,
Finished = 3,
};
// Placeholder only: the guest ABI layout is not known and is never read by this HLE stub.
struct ScePlayerInvitationDialogParam {
std::uint8_t reserved[1];
};
extern "C" {
std::int32_t APS5_VABI scePlayerInvitationDialogInitialize(void);
std::int32_t APS5_VABI scePlayerInvitationDialogOpen(const ScePlayerInvitationDialogParam* param);
std::int32_t APS5_VABI scePlayerInvitationDialogUpdateStatus(void);
std::int32_t APS5_VABI scePlayerInvitationDialogGetStatus(void);
std::int32_t APS5_VABI scePlayerInvitationDialogClose(void);
std::int32_t APS5_VABI scePlayerInvitationDialogTerminate(void);
}
#endif

View File

@ -0,0 +1,40 @@
#include "prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.h"
#include <cstdlib>
namespace {
void expect(std::int32_t actual, std::int32_t expected) {
if (actual != expected) {
std::abort();
}
}
}
int main() {
using enum PlayerInvitationDialogStatus;
expect(scePlayerInvitationDialogUpdateStatus(), static_cast<std::int32_t>(None));
expect(scePlayerInvitationDialogClose(), 0);
expect(scePlayerInvitationDialogTerminate(), 0);
expect(scePlayerInvitationDialogInitialize(), 0);
expect(scePlayerInvitationDialogInitialize(), 0);
expect(scePlayerInvitationDialogGetStatus(), static_cast<std::int32_t>(Initialized));
expect(scePlayerInvitationDialogUpdateStatus(), static_cast<std::int32_t>(Initialized));
ScePlayerInvitationDialogParam param{};
expect(scePlayerInvitationDialogOpen(&param), 0);
expect(scePlayerInvitationDialogGetStatus(), static_cast<std::int32_t>(Running));
expect(scePlayerInvitationDialogUpdateStatus(), static_cast<std::int32_t>(Finished));
expect(scePlayerInvitationDialogUpdateStatus(), static_cast<std::int32_t>(Finished));
expect(scePlayerInvitationDialogOpen(nullptr), 0);
expect(scePlayerInvitationDialogClose(), 0);
expect(scePlayerInvitationDialogGetStatus(), static_cast<std::int32_t>(Finished));
expect(scePlayerInvitationDialogTerminate(), 0);
expect(scePlayerInvitationDialogGetStatus(), static_cast<std::int32_t>(None));
expect(scePlayerInvitationDialogOpen(nullptr), 0);
expect(scePlayerInvitationDialogUpdateStatus(), static_cast<std::int32_t>(Finished));
expect(scePlayerInvitationDialogTerminate(), 0);
}