feat(libSceNetCtlApDialog): run the offline access-point dialog lifecycle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
p3nicillin 2026-10-12 20:02:59 +01:00
parent d53960612f
commit dbecde00e0
4 changed files with 150 additions and 14 deletions

View File

@ -206,6 +206,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSigninDialog>")
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:$<TARGET_FILE_DIR:libSceNetCtlApDialog>")
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)

View File

@ -1,38 +1,78 @@
#include <atomic>
#include <cstdint>
#include <cstddef>
#include <cstring>
#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<int>(0x80B80003u);
constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x80B80004u);
constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast<int>(0x80B80005u);
constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast<int>(0x80B80007u);
constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast<int>(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<int> 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;
}
}

View File

@ -0,0 +1,81 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
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<int>(0x80B80003);
constexpr int kErrAlreadyInitialized = static_cast<int>(0x80B80004);
constexpr int kErrNotFinished = static_cast<int>(0x80B80005);
constexpr int kErrArgNull = static_cast<int>(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);
}

View File

@ -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