Merge branch 'shadps4-emu:main' into fix/windows-condvar-stale-wake-guard

This commit is contained in:
Valdis Bogdāns 2026-07-06 23:30:44 +03:00 committed by GitHub
commit 745304bd91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 3604 additions and 782 deletions

View File

@ -363,9 +363,9 @@ set(KERNEL_LIB src/core/libraries/kernel/coredump/coredump.cpp
src/core/libraries/kernel/aio.h
)
if (ARCHITECTURE STREQUAL "x86_64")
if (ARCHITECTURE STREQUAL "x86_64" OR ARCHITECTURE STREQUAL "arm64")
list(APPEND KERNEL_LIB src/core/libraries/kernel/threads/stack.S)
set_source_files_properties(src/core/libraries/kernel/threads/stack.s PROPERTIES COMPILE_OPTIONS -Wno-unused-command-line-argument)
set_source_files_properties(src/core/libraries/kernel/threads/stack.S PROPERTIES COMPILE_OPTIONS -Wno-unused-command-line-argument)
endif()
set(NETWORK_LIBS src/core/libraries/network/http.cpp
@ -516,6 +516,10 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/video_recording/video_recording.cpp
src/core/libraries/video_recording/video_recording.h
src/core/libraries/video_recording/video_recording_error.h
src/core/libraries/invitation_dialog/invitation_dialog.cpp
src/core/libraries/invitation_dialog/invitation_dialog.h
src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp
src/core/libraries/invitation_dialog/invitation_dialog_ui.h
)
set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h
@ -638,7 +642,7 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_matching2/np_matching2_mm.h
src/core/libraries/np/np_matching2/np_matching2_signaling.cpp
src/core/libraries/np/np_matching2/np_matching2_signaling.h
src/core/libraries/np/np_matching2/np_matching2_types.h
src/core/libraries/np/np_types2.h
src/core/libraries/np/np_trophy.cpp
src/core/libraries/np/np_trophy.h
src/core/libraries/np/np_tus.cpp
@ -1140,6 +1144,8 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/imgui_translations.h
src/imgui/friends_layer.cpp
src/imgui/friends_layer.h
src/imgui/invitation_prompt_layer.cpp
src/imgui/invitation_prompt_layer.h
src/imgui/shadnet_notifications_layer.cpp
src/imgui/shadnet_notifications_layer.h
src/imgui/notifications_layer.cpp

View File

@ -52,6 +52,7 @@ constexpr auto Lib_Http = "Lib.Http"; ///< The Lib
constexpr auto Lib_Http2 = "Lib.Http2"; ///< The LibSceHttp2 implementation.
constexpr auto Lib_Ime = "Lib.Ime"; ///< The LibSceIme implementation
constexpr auto Lib_ImeDialog = "Lib.ImeDialog"; ///< The LibSceImeDialog implementation.
constexpr auto Lib_InvitationDialog="Lib.InvitationDialog"; ///< The LibSceInvitationDialog implementation.
constexpr auto Lib_Jpeg = "Lib.Jpeg"; ///< The LibSceJpeg implementation.
constexpr auto Lib_Kernel = "Lib.Kernel"; ///< The LibKernel implementation.
constexpr auto Lib_LibcInternal = "Lib.LibcInternal"; ///< The LibSceLibcInternal implementation.

View File

@ -76,6 +76,7 @@ std::unordered_map<std::string_view, std::shared_ptr<spdlog::logger>> ALL_LOGGER
{Class::Lib_Http2, nullptr},
{Class::Lib_Ime, nullptr},
{Class::Lib_ImeDialog, nullptr},
{Class::Lib_InvitationDialog, nullptr},
{Class::Lib_Jpeg, nullptr},
{Class::Lib_Kernel, nullptr},
{Class::Lib_LibcInternal, nullptr},

View File

@ -402,7 +402,12 @@ static void ConvertRGBA8888ToRAW8(const u8* src, u8* dst, int width, int height)
s32 PS4_SYSV_ABI sceCameraGetFrameData(s32 handle, OrbisCameraFrameData* frame_data) {
LOG_DEBUG(Lib_Camera, "called");
if (handle < 1 || frame_data == nullptr || frame_data->sizeThis > 584) {
if (frame_data == nullptr) {
return ORBIS_CAMERA_ERROR_PARAM;
}
frame_data->status[0] = -1;
frame_data->status[1] = -1;
if (handle < 1 || frame_data->sizeThis > 584) {
return ORBIS_CAMERA_ERROR_PARAM;
}
if (!g_library_opened || !sdl_camera) {
@ -418,6 +423,8 @@ s32 PS4_SYSV_ABI sceCameraGetFrameData(s32 handle, OrbisCameraFrameData* frame_d
}
frame = SDL_AcquireCameraFrame(sdl_camera, &timestampNS);
frame_data->status[0] = frame != nullptr ? 0 : -1;
frame_data->status[1] = frame != nullptr ? 0 : -1;
if (!frame) {
return ORBIS_CAMERA_ERROR_BUSY;
}
@ -454,6 +461,14 @@ s32 PS4_SYSV_ABI sceCameraGetFrameData(s32 handle, OrbisCameraFrameData* frame_d
}
frame_data->meta.format[0][0] = output_config0.format.formatLevel0;
frame_data->meta.format[1][0] = output_config1.format.formatLevel0;
// on older firmwares, this wasn't present, and the original library also checks struct size
// instead of the SDK version, and without this check, we'd smash the stack in those games
if (frame_data->sizeThis == 584) {
// not fully correct, but good enough
frame_data->pFramePointerListGarlic[0][0] = frame_data->pFramePointerList[0][0];
frame_data->pFramePointerListGarlic[1][0] = frame_data->pFramePointerList[1][0];
}
return ORBIS_OK;
}
@ -642,7 +657,7 @@ s32 PS4_SYSV_ABI sceCameraIsValidFrameData(s32 handle, OrbisCameraFrameData* fra
return ORBIS_CAMERA_ERROR_NOT_OPEN;
}
return 1; // valid
return frame_data->status[0] == 0 && frame_data->status[1] == 0;
}
s32 PS4_SYSV_ABI sceCameraOpen(Libraries::UserService::OrbisUserServiceUserId user_id, s32 type,

View File

@ -0,0 +1,448 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_handler.h"
#include "invitation_dialog_ui.h"
#include "magic_enum/magic_enum.hpp"
namespace Libraries::InvitationDialog {
static auto g_status = Libraries::CommonDialog::Status::NONE;
static InvitationDialogUi g_dialog_ui;
static DialogState g_state;
s32 PS4_SYSV_ABI sceInvitationDialogGetResultA(OrbisInvitationDialogResultA* result) {
LOG_DEBUG(Lib_InvitationDialog, "Getting invitation dialog result (async), result ptr={}",
fmt::ptr(result));
if (g_status == Libraries::CommonDialog::Status::NONE) {
return static_cast<s32>(Libraries::CommonDialog::Error::NOT_INITIALIZED);
}
if (g_status != Libraries::CommonDialog::Status::FINISHED) {
return static_cast<s32>(Libraries::CommonDialog::Error::NOT_FINISHED);
}
if (result == nullptr) {
return static_cast<s32>(Libraries::CommonDialog::Error::ARG_NULL);
}
if (g_state.error_code != 0) {
LOG_ERROR(Lib_InvitationDialog, "errorCode {} block all players", g_state.error_code);
return ORBIS_INVITATION_DIALOG_ERROR_BLOCKED_ALL_PLAYERS;
}
result->callbackArg = g_state.callback_arg;
result->errorCode = g_state.error_code;
result->result = g_state.result;
// sentUsers carries {onlineId, accountId} pairs; report whichever recipient form was sent --
// online IDs (picker / non-A fixed list) or account IDs (A fixed list). Only one is populated.
if (result->sentUsers != nullptr) {
u32 count =
static_cast<u32>(g_state.sent_online_ids.size() > g_state.sent_account_ids.size()
? g_state.sent_online_ids.size()
: g_state.sent_account_ids.size());
if (count > ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE) {
count = ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE;
}
for (u32 i = 0; i < count; i++) {
auto& u = result->sentUsers->users[i];
std::memset(&u, 0, sizeof(u));
if (i < g_state.sent_online_ids.size()) {
std::strncpy(u.onlineId.data, g_state.sent_online_ids[i].c_str(),
sizeof(u.onlineId.data) - 1);
}
if (i < g_state.sent_account_ids.size()) {
u.accountId = g_state.sent_account_ids[i];
}
}
result->sentUsers->count =
(g_state.result == Libraries::CommonDialog::Result::OK) ? count : 0;
}
return static_cast<s32>(g_state.result);
}
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceInvitationDialogOpenA(const OrbisInvitationDialogParamA* param) {
LOG_DEBUG(Lib_InvitationDialog, "sceInvitationDialogOpenA() called (async version)");
LOG_INFO(Lib_InvitationDialog, "Opening invitation dialog asynchronously, param ptr={}",
fmt::ptr(param));
if (!param) {
LOG_ERROR(Lib_InvitationDialog, "Parameter is NULL");
return Libraries::CommonDialog::Error::ARG_NULL;
}
LOG_INFO(Lib_InvitationDialog, "ParamA size={:#x}", param->size);
LOG_INFO(Lib_InvitationDialog, "ParamA mode={}", param->mode);
// Validate mode
if (param->mode != ORBIS_INVITATION_DIALOG_MODE_SEND &&
param->mode != ORBIS_INVITATION_DIALOG_MODE_RECV) {
LOG_ERROR(Lib_InvitationDialog, "Invalid mode: {} (expected SEND={} or RECV={})",
param->mode, ORBIS_INVITATION_DIALOG_MODE_SEND,
ORBIS_INVITATION_DIALOG_MODE_RECV);
return Libraries::CommonDialog::Error::PARAM_INVALID;
}
LOG_INFO(Lib_InvitationDialog, "ParamA userId={:#x}", param->userId);
LOG_INFO(Lib_InvitationDialog, "ParamA callbackArg={}", fmt::ptr(param->callbackArg));
LOG_INFO(Lib_InvitationDialog, "ParamA dataParam={}", fmt::ptr(param->dataParam));
// Log data parameters if present
if (param->dataParam) {
LOG_INFO(Lib_InvitationDialog, "DataParamA pointer: {}", fmt::ptr(param->dataParam));
if (param->mode == ORBIS_INVITATION_DIALOG_MODE_SEND) {
LOG_INFO(Lib_InvitationDialog, "SendInfo:");
if (param->dataParam->SendInfo.userMessage) {
LOG_INFO(Lib_InvitationDialog, " userMessage: '{}'",
param->dataParam->SendInfo.userMessage);
}
if (param->dataParam->SendInfo.sessionId) {
LOG_INFO(Lib_InvitationDialog, " sessionId: {}",
param->dataParam->SendInfo.sessionId->data);
}
// Log address parameters
const auto& addressParam = param->dataParam->SendInfo.addressParam;
LOG_INFO(Lib_InvitationDialog, " addressType={}", addressParam.addressType);
if (addressParam.addressType == ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERDISABLE) {
LOG_INFO(Lib_InvitationDialog, " UserSelectDisableAddress:");
LOG_INFO(Lib_InvitationDialog, " accountIds={}",
fmt::ptr(addressParam.addressInfo.UserSelectDisableAddress.accountIds));
LOG_INFO(Lib_InvitationDialog, " accountIdsCount={}",
addressParam.addressInfo.UserSelectDisableAddress.accountIdsCount);
} else if (addressParam.addressType ==
ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERENABLE) {
LOG_INFO(Lib_InvitationDialog, " UserSelectEnableAddress:");
LOG_INFO(Lib_InvitationDialog, " userMaxCount={}",
addressParam.addressInfo.UserSelectEnableAddress.userMaxCount);
} else {
LOG_WARNING(Lib_InvitationDialog, " Invalid addressType: {}",
addressParam.addressType);
}
} else if (param->mode == ORBIS_INVITATION_DIALOG_MODE_RECV) {
LOG_INFO(Lib_InvitationDialog, "RecvInfo (receive mode - no additional data)");
}
}
// Check initialization state
if (g_status != Libraries::CommonDialog::Status::INITIALIZED &&
g_status != Libraries::CommonDialog::Status::FINISHED) {
LOG_INFO(Lib_InvitationDialog, "Called without proper initialization (status={})",
magic_enum::enum_name(g_status));
return Libraries::CommonDialog::Error::INVALID_STATE;
}
g_state = DialogState{};
g_state.mode = param->mode;
g_state.callback_arg = param->callbackArg;
g_state.user_id = param->userId;
if (param->mode == ORBIS_INVITATION_DIALOG_MODE_SEND) {
if (param->dataParam == nullptr) {
return Libraries::CommonDialog::Error::PARAM_INVALID;
}
const auto& send = param->dataParam->SendInfo;
if (send.sessionId != nullptr) {
g_state.session_id = send.sessionId->data;
}
if (send.userMessage != nullptr) {
g_state.message = send.userMessage;
}
// USERDISABLE: the app fixes the recipient list up front (account IDs). USERENABLE: the
// user picks them in the dialog via the searchable friend picker.
if (send.addressParam.addressType == ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERENABLE) {
g_state.user_editable = true;
} else if (send.addressParam.addressType ==
ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERDISABLE) {
const auto& addr = send.addressParam.addressInfo.UserSelectDisableAddress;
u32 count = addr.accountIdsCount;
if (count > ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE) {
count = ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE;
}
for (u32 i = 0; i < count && addr.accountIds != nullptr; i++) {
g_state.account_ids.push_back(addr.accountIds[i]);
}
}
LOG_INFO(Lib_InvitationDialog, "OpenA(SEND) user={} sessionId='{}' recipients={}",
param->userId, g_state.session_id, g_state.account_ids.size());
} else {
g_state.recv_invitations =
Libraries::Np::NpHandler::GetInstance().GetPendingInvitations(param->userId);
g_state.recv_selected = g_state.recv_invitations.empty() ? -1 : 0;
LOG_INFO(Lib_InvitationDialog, "OpenA(RECV) user={} pending={}", param->userId,
g_state.recv_invitations.size());
}
g_status = Libraries::CommonDialog::Status::RUNNING;
g_dialog_ui = InvitationDialogUi{&g_status, &g_state};
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceInvitationDialogOpen(const OrbisInvitationDialogParam* param) {
LOG_DEBUG(Lib_InvitationDialog, "Opening invitation dialog, param ptr={}", fmt::ptr(param));
if (g_status != Libraries::CommonDialog::Status::INITIALIZED &&
g_status != Libraries::CommonDialog::Status::FINISHED) {
LOG_ERROR(Lib_InvitationDialog, "called without initialize");
return Libraries::CommonDialog::Error::INVALID_STATE;
}
if (!param) {
LOG_ERROR(Lib_InvitationDialog, "Parameter is NULL");
return Libraries::CommonDialog::Error::ARG_NULL;
}
LOG_INFO(Lib_InvitationDialog,
"size={:#x} mode={} userId={} Param callbackArg={} Param dataParam={}", param->size,
param->mode, param->userId, fmt::ptr(param->callbackArg), fmt::ptr(param->dataParam));
// Validate mode
if (param->mode != ORBIS_INVITATION_DIALOG_MODE_SEND &&
param->mode != ORBIS_INVITATION_DIALOG_MODE_RECV) {
LOG_ERROR(Lib_InvitationDialog, "Invalid mode: {} (expected SEND={} or RECV={})",
param->mode, ORBIS_INVITATION_DIALOG_MODE_SEND,
ORBIS_INVITATION_DIALOG_MODE_RECV);
return Libraries::CommonDialog::Error::PARAM_INVALID;
}
// Log data parameters if present
if (param->dataParam) {
LOG_INFO(Lib_InvitationDialog, "DataParam pointer: {}", fmt::ptr(param->dataParam));
if (param->mode == ORBIS_INVITATION_DIALOG_MODE_SEND) {
LOG_INFO(Lib_InvitationDialog, "SendInfo:");
if (param->dataParam->SendInfo.userMessage) {
LOG_INFO(Lib_InvitationDialog, " userMessage: '{}'",
param->dataParam->SendInfo.userMessage);
}
if (param->dataParam->SendInfo.sessionId) {
LOG_INFO(Lib_InvitationDialog, " sessionId: {}",
param->dataParam->SendInfo.sessionId->data);
}
// Log address parameters
const auto& addressParam = param->dataParam->SendInfo.addressParam;
LOG_INFO(Lib_InvitationDialog, " addressType={}", addressParam.addressType);
if (addressParam.addressType == ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERDISABLE) {
LOG_INFO(Lib_InvitationDialog, " UserSelectDisableAddress:");
LOG_INFO(Lib_InvitationDialog, " onlineIds={}",
fmt::ptr(addressParam.addressInfo.UserSelectDisableAddress.onlineIds));
LOG_INFO(Lib_InvitationDialog, " onlineIdsCount={}",
addressParam.addressInfo.UserSelectDisableAddress.onlineIdsCount);
} else if (addressParam.addressType ==
ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERENABLE) {
LOG_INFO(Lib_InvitationDialog, " UserSelectEnableAddress:");
LOG_INFO(Lib_InvitationDialog, " onlineIdsMaxCount={}",
addressParam.addressInfo.UserSelectEnableAddress.onlineIdsMaxCount);
} else {
LOG_WARNING(Lib_InvitationDialog, " Invalid addressType: {}",
addressParam.addressType);
}
} else if (param->mode == ORBIS_INVITATION_DIALOG_MODE_RECV) {
LOG_INFO(Lib_InvitationDialog, "RecvInfo (receive mode - no additional data)");
}
}
g_state = DialogState{};
g_state.mode = param->mode;
g_state.callback_arg = param->callbackArg;
g_state.user_id = param->userId;
if (param->mode == ORBIS_INVITATION_DIALOG_MODE_SEND) {
if (param->dataParam == nullptr) {
return Libraries::CommonDialog::Error::PARAM_INVALID;
}
const auto& send = param->dataParam->SendInfo;
if (send.sessionId != nullptr) {
g_state.session_id = send.sessionId->data;
}
if (send.userMessage != nullptr) {
g_state.message = send.userMessage;
}
// USERDISABLE: the app fixes the recipient list up front. USERENABLE: the user picks them
// in the dialog via the searchable friend picker.
if (send.addressParam.addressType == ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERENABLE) {
g_state.user_editable = true;
} else if (send.addressParam.addressType ==
ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERDISABLE) {
const auto& addr = send.addressParam.addressInfo.UserSelectDisableAddress;
u32 count = addr.onlineIdsCount;
if (count > ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE) {
count = ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE;
}
if (count != 0 && addr.onlineIds != nullptr) {
g_state.online_ids.assign(addr.onlineIds, addr.onlineIds + count);
}
}
LOG_INFO(Lib_InvitationDialog, "Open(SEND) user={} sessionId='{}' recipients={}",
param->userId, g_state.session_id, g_state.online_ids.size());
} else {
auto pend = Libraries::Np::NpHandler::GetInstance().GetPendingInvitations(param->userId);
g_state.recv_invitations = std::move(pend);
g_state.recv_selected = g_state.recv_invitations.empty() ? -1 : 0;
LOG_INFO(Lib_InvitationDialog, "Open(RECV) user={} pending={}", param->userId,
g_state.recv_invitations.size());
}
g_status = Libraries::CommonDialog::Status::RUNNING;
g_dialog_ui = InvitationDialogUi{&g_status, &g_state};
return Libraries::CommonDialog::Error::OK;
}
s32 PS4_SYSV_ABI sceInvitationDialogGetResult(OrbisInvitationDialogResult* result) {
LOG_DEBUG(Lib_InvitationDialog, "Getting invitation dialog result, result ptr={}",
fmt::ptr(result));
if (g_status == Libraries::CommonDialog::Status::NONE) {
return static_cast<s32>(Libraries::CommonDialog::Error::NOT_INITIALIZED);
}
if (g_status != Libraries::CommonDialog::Status::FINISHED) {
return static_cast<s32>(Libraries::CommonDialog::Error::NOT_FINISHED);
}
if (result == nullptr) {
return static_cast<s32>(Libraries::CommonDialog::Error::ARG_NULL);
}
if (g_state.error_code != 0) {
LOG_ERROR(Lib_InvitationDialog, "errorCode {} block all players", g_state.error_code);
return ORBIS_INVITATION_DIALOG_ERROR_BLOCKED_ALL_PLAYERS;
}
// Log result fields if they contain data
if (result->callbackArg) {
LOG_INFO(Lib_InvitationDialog, "Result callbackArg={}", fmt::ptr(result->callbackArg));
}
LOG_INFO(Lib_InvitationDialog, "Result errorCode={:#x}", result->errorCode);
LOG_INFO(Lib_InvitationDialog, "Result status={}", magic_enum::enum_name(result->result));
if (result->sentOnlineIds) {
LOG_INFO(Lib_InvitationDialog, "Result sentOnlineIds count={}",
result->sentOnlineIds->count);
for (u32 i = 0; i < result->sentOnlineIds->count &&
i < ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE;
i++) {
LOG_INFO(Lib_InvitationDialog, " sentOnlineIds[{}]: {}", i,
result->sentOnlineIds->onlineId[i].data);
}
}
result->callbackArg = g_state.callback_arg;
result->errorCode = g_state.error_code;
result->result = g_state.result;
if (result->sentOnlineIds != nullptr) {
u32 count = static_cast<u32>(g_state.sent_online_ids.size());
if (count > ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE) {
count = ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE;
}
for (u32 i = 0; i < count; i++) {
auto& oid = result->sentOnlineIds->onlineId[i];
std::memset(&oid, 0, sizeof(oid));
std::strncpy(oid.data, g_state.sent_online_ids[i].c_str(), sizeof(oid.data) - 1);
}
result->sentOnlineIds->count =
(g_state.result == Libraries::CommonDialog::Result::OK) ? count : 0;
}
return static_cast<s32>(g_state.result);
}
Libraries::CommonDialog::Status PS4_SYSV_ABI sceInvitationDialogUpdateStatus() {
LOG_DEBUG(Lib_InvitationDialog, "UpdateStatus called, current status={}",
magic_enum::enum_name(g_status));
return g_status;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI sceInvitationDialogClose() {
LOG_DEBUG(Lib_InvitationDialog, "Closing invitation dialog, current status={}",
magic_enum::enum_name(g_status));
if (g_status != Libraries::CommonDialog::Status::RUNNING) {
LOG_WARNING(Lib_InvitationDialog, "Cannot close dialog: not running (status={})",
magic_enum::enum_name(g_status));
return Libraries::CommonDialog::Error::NOT_RUNNING;
}
g_dialog_ui.Finish(Libraries::CommonDialog::Result::OK);
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI sceInvitationDialogTerminate() {
LOG_DEBUG(Lib_InvitationDialog, "Terminating invitation dialog system, current status={}",
magic_enum::enum_name(g_status));
if (g_status == Libraries::CommonDialog::Status::RUNNING) {
LOG_DEBUG(Lib_InvitationDialog, "Dialog is running, closing before termination");
sceInvitationDialogClose();
}
if (g_status == Libraries::CommonDialog::Status::NONE) {
LOG_ERROR(Lib_InvitationDialog, "Cannot terminate: not initialized");
return Libraries::CommonDialog::Error::NOT_INITIALIZED;
}
g_status = Libraries::CommonDialog::Status::NONE;
CommonDialog::g_isUsed = false;
LOG_DEBUG(Lib_InvitationDialog, "Invitation dialog terminated successfully");
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Status PS4_SYSV_ABI sceInvitationDialogGetStatus() {
LOG_DEBUG(Lib_InvitationDialog, "Getting invitation dialog status: {}",
magic_enum::enum_name(g_status));
return g_status;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI sceInvitationDialogInitialize() {
LOG_DEBUG(Lib_InvitationDialog, "called");
if (!CommonDialog::g_isInitialized) {
LOG_ERROR(Lib_InvitationDialog, "CommonDialog system not initialized");
return Libraries::CommonDialog::Error::NOT_SYSTEM_INITIALIZED;
}
if (g_status != Libraries::CommonDialog::Status::NONE) {
LOG_ERROR(Lib_InvitationDialog, "Invitation dialog already initialized (status={})",
magic_enum::enum_name(g_status));
return Libraries::CommonDialog::Error::ALREADY_INITIALIZED;
}
if (CommonDialog::g_isUsed) {
LOG_ERROR(Lib_InvitationDialog, "CommonDialog already in use");
return Libraries::CommonDialog::Error::BUSY;
}
g_status = Libraries::CommonDialog::Status::INITIALIZED;
CommonDialog::g_isUsed = true;
LOG_INFO(Lib_InvitationDialog, "Invitation dialog initialized successfully");
return Libraries::CommonDialog::Error::OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("WWtCL5lzi7Y", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogClose);
LIB_FUNCTION("8XKR6wa64iQ", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogGetResult);
LIB_FUNCTION("WuuUhuKOxwQ", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogGetResultA);
LIB_FUNCTION("EiF92YDNHRA", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogGetStatus);
LIB_FUNCTION("XvA5KS56wcs", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogInitialize);
LIB_FUNCTION("0zU0G+wiVLA", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogOpen);
LIB_FUNCTION("sAxbHhAWMXM", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogOpenA);
LIB_FUNCTION("B6HVJtDYxEE", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogTerminate);
LIB_FUNCTION("9+g9iOq+7kg", "libSceInvitationDialog", 1, "libSceInvitationDialog",
sceInvitationDialogUpdateStatus);
LIB_FUNCTION("8XKR6wa64iQ", "libSceInvitationDialogCompat", 1, "libSceInvitationDialog",
sceInvitationDialogGetResult);
LIB_FUNCTION("0zU0G+wiVLA", "libSceInvitationDialogCompat", 1, "libSceInvitationDialog",
sceInvitationDialogOpen);
};
} // namespace Libraries::InvitationDialog

View File

@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <core/libraries/np/np_types.h>
#include <core/libraries/system/commondialog.h>
#include <core/libraries/system/userservice.h>
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::InvitationDialog {
constexpr int ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE = 16;
constexpr int ORBIS_INVITATION_DIALOG_MAX_USER_MSG_SIZE = 512;
constexpr int ORBIS_NP_SESSION_ID_MAX_SIZE = 45;
struct OrbisInvitationDialogUserList {
u32 count;
struct {
Libraries::Np::OrbisNpOnlineId onlineId;
Libraries::Np::OrbisNpAccountId accountId;
} users[ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE];
};
struct SceInvitationDialogOnlineIdList {
u32 count;
Libraries::Np::OrbisNpOnlineId onlineId[ORBIS_INVITATION_DIALOG_ADDRESS_USER_LIST_MAX_SIZE];
};
constexpr int ORBIS_INVITATION_DIALOG_ERROR_BLOCKED_ALL_PLAYERS = 0x810A0001; // if errorCode !=0
struct OrbisInvitationDialogResultA {
void* callbackArg;
s32 errorCode;
Libraries::CommonDialog::Result result;
OrbisInvitationDialogUserList* sentUsers;
u8 reserved[32];
};
struct OrbisInvitationDialogResult {
void* callbackArg;
s32 errorCode;
Libraries::CommonDialog::Result result;
SceInvitationDialogOnlineIdList* sentOnlineIds;
u8 reserved[32];
};
// dialog mode
constexpr int ORBIS_INVITATION_DIALOG_MODE_INVALID = 0; // Invalid mode / initial value
constexpr int ORBIS_INVITATION_DIALOG_MODE_SEND = 1; // Send mode
constexpr int ORBIS_INVITATION_DIALOG_MODE_RECV = 2; // Receive mode
union OrbisInvitationDialogAddressInfoA {
struct {
const Libraries::Np::OrbisNpAccountId* accountIds;
u32 accountIdsCount;
int : 32;
} UserSelectDisableAddress;
struct {
u32 userMaxCount;
} UserSelectEnableAddress;
};
union OrbisInvitationDialogAddressInfo {
struct {
const Libraries::Np::OrbisNpOnlineId* onlineIds;
u32 onlineIdsCount;
} UserSelectDisableAddress;
struct {
u32 onlineIdsMaxCount;
} UserSelectEnableAddress;
};
// addressType
constexpr int ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_INVALID = 0; // Invalid mode /initial value
constexpr int ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERDISABLE = 1; // Recipient list editing
// disabled
constexpr int ORBIS_INVITATION_DIALOG_ADDRESS_TYPE_USERENABLE = 2; // Recipient list editing enabled
struct OrbisInvitationDialogAddressParam {
s32 addressType;
OrbisInvitationDialogAddressInfo addressInfo;
};
struct OrbisInvitationDialogAddressParamA {
s32 addressType;
int : 32;
OrbisInvitationDialogAddressInfoA addressInfo;
};
struct OrbisNpSessionId {
char data[ORBIS_NP_SESSION_ID_MAX_SIZE];
char term;
char padding[2];
};
constexpr int ORBIS_NP_INVITATION_ID_SIZE = 60;
struct OrbisNpInvitationId {
char data[ORBIS_NP_INVITATION_ID_SIZE];
char term;
};
using OrbisNpSessionInvitationEventFlag = s32;
constexpr OrbisNpSessionInvitationEventFlag ORBIS_NP_SESSION_INVITATION_EVENT_FLAG_INVITATION =
0x01;
// Delivered via SceSystemServiceEvent::data::param on ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION
struct OrbisNpSessionInvitationEventParam {
OrbisNpSessionId sessionId;
OrbisNpInvitationId invitationId;
OrbisNpSessionInvitationEventFlag flag;
char padding[4];
Libraries::Np::OrbisNpOnlineId onlineId;
};
static_assert(sizeof(OrbisNpSessionInvitationEventParam) == 0x8c,
"SESSION_INVITATION param must be 140 bytes");
static_assert(offsetof(OrbisNpSessionInvitationEventParam, flag) == 112);
static_assert(offsetof(OrbisNpSessionInvitationEventParam, onlineId) == 120);
union OrbisInvitationDialogDataParam {
struct {
const char* userMessage;
const OrbisNpSessionId* sessionId;
OrbisInvitationDialogAddressParam addressParam;
} SendInfo;
struct {
u8 reserved[64];
} RecvInfo;
};
union OrbisInvitationDialogDataParamA {
struct {
const char* userMessage;
const OrbisNpSessionId* sessionId;
OrbisInvitationDialogAddressParamA addressParam;
} SendInfo;
struct {
u8 reserved[64];
} RecvInfo;
};
struct OrbisInvitationDialogParam {
Libraries::CommonDialog::BaseParam baseParam;
u32 size;
s32 mode;
Libraries::UserService::OrbisUserServiceUserId userId;
void* callbackArg;
OrbisInvitationDialogDataParam* dataParam;
u8 reserved[64];
};
struct OrbisInvitationDialogParamA {
Libraries::CommonDialog::BaseParam baseParam;
u32 size;
s32 mode;
Libraries::UserService::OrbisUserServiceUserId userId;
int : 32;
void* callbackArg;
const OrbisInvitationDialogDataParamA* dataParam;
u8 reserved[64];
};
Libraries::CommonDialog::Error PS4_SYSV_ABI sceInvitationDialogClose();
s32 PS4_SYSV_ABI sceInvitationDialogGetResult(OrbisInvitationDialogResult* result);
s32 PS4_SYSV_ABI sceInvitationDialogGetResultA(OrbisInvitationDialogResultA* result);
Libraries::CommonDialog::Status PS4_SYSV_ABI sceInvitationDialogGetStatus();
Libraries::CommonDialog::Error PS4_SYSV_ABI sceInvitationDialogInitialize();
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceInvitationDialogOpen(const OrbisInvitationDialogParam* param);
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceInvitationDialogOpenA(const OrbisInvitationDialogParamA* param);
Libraries::CommonDialog::Error PS4_SYSV_ABI sceInvitationDialogTerminate();
Libraries::CommonDialog::Status PS4_SYSV_ABI sceInvitationDialogUpdateStatus();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::InvitationDialog

View File

@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <imgui.h>
#include "common/logging/log.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/invitation_dialog/invitation_dialog_ui.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/ssl.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_std.h"
static constexpr ImVec2 BUTTON_SIZE{130.0f, 30.0f};
namespace Libraries::InvitationDialog {
using CommonDialog::Result;
using CommonDialog::Status;
InvitationDialogUi::InvitationDialogUi(Status* status, DialogState* state)
: status(status), state(state) {
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
InvitationDialogUi::~InvitationDialogUi() {
Finish(Result::OK);
}
InvitationDialogUi::InvitationDialogUi(InvitationDialogUi&& other) noexcept
: Layer(other), first_render(other.first_render), status(other.status), state(other.state) {
other.status = nullptr;
other.state = nullptr;
}
InvitationDialogUi& InvitationDialogUi::operator=(InvitationDialogUi&& other) noexcept {
first_render = other.first_render;
status = other.status;
state = other.state;
other.status = nullptr;
other.state = nullptr;
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
return *this;
}
void InvitationDialogUi::Finish(Result result) {
if (state) {
state->result = result;
}
if (status) {
*status = Status::FINISHED;
}
status = nullptr;
RemoveLayer(this);
}
void InvitationDialogUi::Draw() {
using namespace ImGui;
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
return;
}
const auto& io = GetIO();
const ImVec2 window_size{
std::min(io.DisplaySize.x, 500.0f),
std::min(io.DisplaySize.y, 320.0f),
};
CentralizeNextWindow();
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
KeepNavHighlight();
if (Begin("Invitation Dialog##InvitationDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
DrawPrettyBackground();
const bool send_mode = state->mode == ORBIS_INVITATION_DIALOG_MODE_SEND;
SetWindowFontScale(1.3f);
Text("%s", send_mode ? "Send Invitation" : "Invitations");
SetWindowFontScale(1.0f);
Separator();
Spacing();
if (send_mode) {
if (!state->session_id.empty()) {
TextWrapped("Session: %s", state->session_id.c_str());
}
if (state->user_editable) {
TextWrapped("Select friends to invite:");
auto& np = Libraries::Np::NpHandler::GetInstance();
const auto friend_list = np.GetFriendList(static_cast<s32>(state->user_id));
if (friend_list.friends.empty()) {
TextDisabled("No friends available. Add friends from the friends overlay.");
} else {
SetNextItemWidth(-1.0f);
InputTextWithHint("##invitation_friend_filter", "Search friends",
state->friend_filter, sizeof(state->friend_filter));
const auto to_lower = [](std::string v) {
std::transform(v.begin(), v.end(), v.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return v;
};
const std::string filter = to_lower(state->friend_filter);
if (BeginChild("##invitation_friends", ImVec2(0.0f, 120.0f), true)) {
auto& sel = state->selected_npids;
for (const auto& f : friend_list.friends) {
if (!filter.empty() &&
to_lower(f.npid).find(filter) == std::string::npos) {
continue; // filtered out of view; selection is unaffected
}
PushID(f.npid.c_str());
const auto it = std::find(sel.begin(), sel.end(), f.npid);
bool checked = it != sel.end();
const std::string label =
f.npid + (f.online ? " (online)" : " (offline)");
if (Checkbox(label.c_str(), &checked)) {
if (checked && it == sel.end()) {
sel.push_back(f.npid);
} else if (!checked && it != sel.end()) {
sel.erase(it);
}
}
PopID();
}
}
EndChild();
}
} else {
// TODO ?
}
if (!state->message.empty()) {
Spacing();
TextWrapped("Message: %s", state->message.c_str());
}
} else {
// RECV: list the pending invitations stashed by NpHandler; the user picks one.
if (state->recv_invitations.empty()) {
TextWrapped("You have no pending invitations.");
} else {
TextWrapped("You have %d pending invitation(s):",
static_cast<int>(state->recv_invitations.size()));
Spacing();
if (BeginChild("##pending_invitations", ImVec2(0.0f, 120.0f), true)) {
for (int i = 0; i < static_cast<int>(state->recv_invitations.size()); i++) {
PushID(i);
const auto& inv = state->recv_invitations[i];
const std::string label =
(inv.from_npid.empty() ? std::string("(unknown)") : inv.from_npid) +
" invited you to a game session.";
if (Selectable(label.c_str(), state->recv_selected == i)) {
state->recv_selected = i;
}
PopID();
}
}
EndChild();
Spacing();
TextDisabled("Accept to join the selected session, or decline to dismiss it.");
}
}
const auto ws = GetWindowSize();
const float y = ws.y - 10.0f - BUTTON_SIZE.y;
if (send_mode) {
const bool no_recipients = state->user_editable && state->selected_npids.empty();
SetCursorPos({ws.x / 2.0f - BUTTON_SIZE.x - 10.0f, y});
BeginDisabled(no_recipients);
if (Button("Send", BUTTON_SIZE)) {
// USERENABLE: send the npids the user picked. USERDISABLE: send the app-fixed
// list resolved at Open -- online IDs (non-A) or account IDs (A); only one is set.
// Resolve recipients: online IDs (picker or non-A fixed list) and/or account IDs
// (A fixed list). Only one form is populated per dialog.
std::vector<std::string> sent_npids;
if (state->user_editable) {
sent_npids = state->selected_npids;
} else {
for (const auto& oid : state->online_ids) {
size_t n = 0;
while (n < sizeof(oid.data) && oid.data[n] != '\0') {
n++;
}
sent_npids.emplace_back(oid.data, n);
}
}
std::vector<std::string> to = sent_npids;
for (const auto id : state->account_ids) {
to.push_back(std::to_string(id));
}
auto& np = Libraries::Np::NpHandler::GetInstance();
const bool ok = np.SendSessionInvitation(static_cast<s32>(state->user_id),
state->session_id, to, state->message);
// Record what was sent for GetResult/GetResultA (online IDs and/or account IDs).
if (ok) {
state->sent_online_ids = sent_npids;
state->sent_account_ids = state->account_ids;
}
Finish(Result::OK);
}
EndDisabled();
if (first_render) {
SetItemCurrentNavFocus();
}
SameLine();
SetCursorPos({ws.x / 2.0f + 10.0f, y});
if (Button("Cancel", BUTTON_SIZE)) {
Finish(Result::USER_CANCELED);
}
} else {
SetCursorPos({ws.x / 2.0f - BUTTON_SIZE.x - 10.0f, y});
const bool no_selection =
state->recv_selected < 0 ||
state->recv_selected >= static_cast<int>(state->recv_invitations.size());
BeginDisabled(no_selection);
if (Button("Accept", BUTTON_SIZE)) {
// Raises the SESSION_INVITATION join event and consumes the invitation server-side.
const auto& inv = state->recv_invitations[state->recv_selected];
const bool ok = Libraries::Np::NpHandler::GetInstance().AcceptSessionInvitation(
static_cast<s32>(state->user_id), inv.invitation_id);
if (ok) {
Finish(Result::OK);
} else {
// Accept failed (expired/already used/network). Drop the dead entry; stay open
// if others remain so the user can pick another, otherwise cancel.
state->recv_invitations.erase(state->recv_invitations.begin() +
state->recv_selected);
state->recv_selected = state->recv_invitations.empty() ? -1 : 0;
if (state->recv_invitations.empty()) {
Finish(Result::USER_CANCELED);
}
}
}
EndDisabled();
if (first_render) {
SetItemCurrentNavFocus();
}
SameLine();
SetCursorPos({ws.x / 2.0f + 10.0f, y});
BeginDisabled(no_selection);
if (Button("Decline", BUTTON_SIZE)) {
// Dismiss the selected invite so it won't reappear; stay open for any others.
const auto& inv = state->recv_invitations[state->recv_selected];
Libraries::Np::NpHandler::GetInstance().DeclineSessionInvitation(
static_cast<s32>(state->user_id), inv.invitation_id);
state->recv_invitations.erase(state->recv_invitations.begin() +
state->recv_selected);
state->recv_selected = state->recv_invitations.empty() ? -1 : 0;
if (state->recv_invitations.empty()) {
Finish(Result::USER_CANCELED);
}
}
EndDisabled();
}
}
End();
first_render = false;
}
} // namespace Libraries::InvitationDialog

View File

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
namespace Libraries::InvitationDialog {
// Data captured at Open(),OpenA, displayed by the UI layer, and read back by
// GetResult()/GetResultA().
struct DialogState {
s32 mode = ORBIS_INVITATION_DIALOG_MODE_INVALID;
CommonDialog::Result result = CommonDialog::Result::OK;
bool user_editable = false; // USERENABLE recipient list (user picks in UI)
std::vector<std::string> selected_npids;
char friend_filter[64] = {};
// RECV: the pending invitations stashed by NpHandler on arrival, plus the selected index
// (-1 when there are none).
std::vector<Libraries::Np::NpHandler::PendingInvitation> recv_invitations;
int recv_selected = -1;
std::string message;
Libraries::UserService::OrbisUserServiceUserId user_id =
Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
std::string session_id;
void* callback_arg = nullptr;
s32 error_code = 0;
// USERDISABLE fixed recipient lists resolved at Open. Only one is populated per dialog:
// online IDs (non-A path) or account IDs (A accountIds path).
std::vector<Libraries::Np::OrbisNpOnlineId> online_ids;
std::vector<Libraries::Np::OrbisNpAccountId> account_ids;
// Recipients actually sent (SEND mode), echoed back by GetResult/GetResultA. Online IDs come
// from the picker or the non-A fixed list; account IDs from the A fixed list.
std::vector<std::string> sent_online_ids;
std::vector<Libraries::Np::OrbisNpAccountId> sent_account_ids;
};
// ImGui layer that renders the invitation dialog.
class InvitationDialogUi final : public ImGui::Layer {
bool first_render{false};
CommonDialog::Status* status{nullptr};
DialogState* state{nullptr};
public:
explicit InvitationDialogUi(CommonDialog::Status* status = nullptr,
DialogState* state = nullptr);
~InvitationDialogUi() override;
InvitationDialogUi(const InvitationDialogUi&) = delete;
InvitationDialogUi& operator=(const InvitationDialogUi&) = delete;
InvitationDialogUi(InvitationDialogUi&& other) noexcept;
InvitationDialogUi& operator=(InvitationDialogUi&& other) noexcept;
// Records the call result and transitions the dialog to FINISHED.
void Finish(CommonDialog::Result result);
void Draw() override;
};
} // namespace Libraries::InvitationDialog

View File

@ -12,7 +12,7 @@
#include "core/libraries/libs.h"
#include "core/memory.h"
#ifdef ARCH_X86_64
#if defined(ARCH_X86_64) || defined(__arm64__) || defined(__aarch64__)
extern "C" void* PS4_SYSV_ABI _runOnAnotherStack(void* arg, void* func,
void* stackb) asm("_runOnAnotherStack");
#else

View File

@ -1,6 +1,8 @@
# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
#if defined(__x86_64__)
.global _runOnAnotherStack
_runOnAnotherStack:
pushq %r12
@ -35,3 +37,41 @@ _runOnAnotherStack:
popq %r13
popq %r12
ret
#elif defined(__arm64__) || defined(__aarch64__)
.global _runOnAnotherStack
_runOnAnotherStack:
// Save frame pointer (x29), link register (x30), and callee-saved registers x19, x20
stp x29, x30, [sp, #-32]!
mov x29, sp
stp x19, x20, [sp, #16]
// Save the current stack pointer into x19 (callee-saved)
mov x19, sp
// Save the current frame pointer into x20 (callee-saved)
mov x20, x29
// Align the target stack pointer (x2) to 16 bytes (ARM64 constraint)
and x2, x2, #-16
// Switch stack pointer to the new stack (x2)
mov sp, x2
// Switch frame pointer to the new stack (x2)
mov x29, x2
// Call the function (target address is in x1)
// Note: the argument is already in x0!
blr x1
// Restore the old stack pointer and frame pointer
mov sp, x19
mov x29, x20
// Restore saved registers and return
ldp x19, x20, [sp, #16]
ldp x29, x30, [sp], #32
ret
#endif

View File

@ -22,6 +22,7 @@
#include "core/libraries/ime/error_dialog.h"
#include "core/libraries/ime/ime.h"
#include "core/libraries/ime/ime_dialog.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libpng/pngdec.h"
@ -158,6 +159,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::VrTracker::RegisterLib(sym);
Libraries::ContentExport::RegisterLib(sym);
Libraries::VideoRecording::RegisterLib(sym);
Libraries::InvitationDialog::RegisterLib(sym);
// Loading libSceSsl is locked behind a title workaround that currently applies to nothing.
// Libraries::Ssl::RegisterLib(sym);

View File

@ -5,16 +5,20 @@
#include <algorithm>
#include <chrono>
#include <cstring>
#include <httplib.h>
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/network/net_upnp.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_score/np_score.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
#include "core/libraries/system/systemservice.h"
#include "core/user_settings.h"
#include "imgui/invitation_prompt_layer.h"
#include "imgui/shadnet_notifications_layer.h"
#include "np_handler.h"
#include "shadnet.pb.h"
@ -372,6 +376,222 @@ std::string NpHandler::GetBearerToken(s32 user_id) const {
return it != m_clients.end() ? it->second->GetBearerToken() : std::string{};
}
// Minimal JSON string escaper for the invitation-request body (npids are safe but the user message
// can contain quotes/backslashes/control chars).
std::string JsonEscape(const std::string& in) {
std::string out;
out.reserve(in.size() + 8);
for (const char c : in) {
switch (c) {
case '"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
case '\r':
out += "\\r";
break;
case '\t':
out += "\\t";
break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
char buf[8];
std::snprintf(buf, sizeof(buf), "\\u%04x", c);
out += buf;
} else {
out += c;
}
}
}
return out;
}
bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id,
const std::vector<std::string>& to,
const std::string& message) {
if (session_id.empty() || to.empty()) {
LOG_ERROR(NpHandler, "SendSessionInvitation: empty session_id or recipient list");
return false;
}
const std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
if (base_url.empty()) {
LOG_ERROR(NpHandler, "SendSessionInvitation: WebAPI server address is empty");
return false;
}
const std::string token = GetBearerToken(user_id);
if (token.empty()) {
LOG_ERROR(NpHandler, "SendSessionInvitation: no bearer token for user_id={}", user_id);
return false;
}
// invitation-request JSON: {"to":[...],"message":"..."}
std::string json = "{\"to\":[";
for (size_t i = 0; i < to.size(); ++i) {
if (i != 0) {
json += ',';
}
json += '"';
json += JsonEscape(to[i]);
json += '"';
}
json += ']';
if (!message.empty()) {
json += ",\"message\":\"";
json += JsonEscape(message);
json += '"';
}
json += '}';
// multipart/mixed with a single JSON part; shadNet keys on the Content-Description.
const std::string boundary = "shadps4invite" + std::to_string(user_id);
std::string body;
body += "--" + boundary + "\r\n";
body += "Content-Type: application/json; charset=utf-8\r\n";
body += "Content-Description: invitation-request\r\n\r\n";
body += json;
body += "\r\n--" + boundary + "--\r\n";
const std::string path = "/v1/sessions/" + session_id + "/invitations";
const std::string content_type = "multipart/mixed; boundary=" + boundary;
httplib::Client cli(base_url);
cli.set_connection_timeout(5);
cli.set_read_timeout(10);
const httplib::Headers headers = {{"Authorization", "Bearer " + token}};
const auto res = cli.Post(path.c_str(), headers, body, content_type.c_str());
if (!res) {
LOG_ERROR(NpHandler, "SendSessionInvitation: POST {} failed (no response)", path);
return false;
}
if (res->status != 200 && res->status != 204) {
LOG_ERROR(NpHandler, "SendSessionInvitation: POST {} -> HTTP {}", path, res->status);
return false;
}
LOG_INFO(NpHandler, "SendSessionInvitation: sent invite to {} recipient(s) for session '{}'",
to.size(), session_id);
return true;
}
void NpHandler::PostSessionInvitationEvent(const std::string& session_id,
const std::string& invitation_id,
const std::string& accepter_online_id) {
using Libraries::InvitationDialog::ORBIS_NP_SESSION_INVITATION_EVENT_FLAG_INVITATION;
using Libraries::InvitationDialog::OrbisNpSessionInvitationEventParam;
Libraries::SystemService::OrbisSystemServiceEvent event{};
event.event_type = Libraries::SystemService::OrbisSystemServiceEventType::SessionInvitation;
auto* param = reinterpret_cast<OrbisNpSessionInvitationEventParam*>(event.param);
std::memset(param, 0, sizeof(*param));
std::strncpy(param->sessionId.data, session_id.c_str(), sizeof(param->sessionId.data) - 1);
if (!invitation_id.empty()) {
std::strncpy(param->invitationId.data, invitation_id.c_str(),
sizeof(param->invitationId.data) - 1);
param->flag = ORBIS_NP_SESSION_INVITATION_EVENT_FLAG_INVITATION;
} else {
param->flag = 0; // join from session info (no invitation id in the push)
}
std::strncpy(param->onlineId.data, accepter_online_id.c_str(),
sizeof(param->onlineId.data) - 1);
Libraries::SystemService::PushSystemServiceEvent(event);
LOG_INFO(NpHandler, "Posted SESSION_INVITATION session='{}' flag={} onlineId='{}'", session_id,
param->flag, accepter_online_id);
}
std::vector<NpHandler::PendingInvitation> NpHandler::GetPendingInvitations(s32 user_id) const {
std::lock_guard lk(m_mutex_pending_invites);
const auto it = m_pending_invites.find(user_id);
if (it == m_pending_invites.end()) {
return {};
}
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
std::vector<PendingInvitation> out;
out.reserve(it->second.size());
for (const auto& p : it->second) {
if (p.valid_until == 0 || now <= p.valid_until) {
out.push_back(p);
}
}
return out;
}
bool NpHandler::AcceptSessionInvitation(s32 user_id, const std::string& invitation_id) {
// Pull the matching stashed invite (and drop it).
PendingInvitation inv;
bool found = false;
{
std::lock_guard lk(m_mutex_pending_invites);
const auto mit = m_pending_invites.find(user_id);
if (mit != m_pending_invites.end()) {
auto& v = mit->second;
for (auto it = v.begin(); it != v.end(); ++it) {
if (it->invitation_id == invitation_id) {
inv = *it;
v.erase(it);
found = true;
break;
}
}
}
}
// Whatever surface handled it (RECV dialog or the emulator prompt), retire the other one.
ImGui::InvitationPrompt::Dismiss(invitation_id);
if (!found) {
LOG_ERROR(NpHandler, "AcceptSessionInvitation: no pending invite '{}' for user_id={}",
invitation_id, user_id);
return false;
}
// Raise the join event now that the user has explicitly accepted (via the RECV dialog or the
// emulator's system-UI equivalent).
PostSessionInvitationEvent(inv.session_id, invitation_id, inv.to_npid);
// Consume it server-side (PUT usedFlag=true).
const std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
const std::string token = GetBearerToken(user_id);
if (base_url.empty() || token.empty()) {
LOG_ERROR(NpHandler, "AcceptSessionInvitation: no WebAPI server/token for user_id={}",
user_id);
return false;
}
httplib::Client cli(base_url);
cli.set_connection_timeout(5);
cli.set_read_timeout(10);
const httplib::Headers headers = {{"Authorization", "Bearer " + token}};
const std::string path = "/v1/users/me/invitations/" + invitation_id;
const auto res = cli.Put(path.c_str(), headers, "{\"usedFlag\":true}", "application/json");
if (!res || (res->status != 200 && res->status != 204)) {
LOG_ERROR(NpHandler, "AcceptSessionInvitation: PUT {} failed ({})", path,
res ? res->status : 0);
return false;
}
LOG_INFO(NpHandler, "AcceptSessionInvitation: consumed '{}' session='{}'", invitation_id,
inv.session_id);
return true;
}
void NpHandler::DeclineSessionInvitation(s32 user_id, const std::string& invitation_id) {
ImGui::InvitationPrompt::Dismiss(invitation_id);
std::lock_guard lock(m_mutex_pending_invites);
auto uit = m_pending_invites.find(user_id);
if (uit == m_pending_invites.end()) {
return;
}
auto& v = uit->second;
v.erase(std::remove_if(
v.begin(), v.end(),
[&](const PendingInvitation& p) { return p.invitation_id == invitation_id; }),
v.end());
LOG_INFO(NpHandler, "DeclineSessionInvitation: dismissed '{}' for user_id={}", invitation_id,
user_id);
}
u32 NpHandler::GetLocalIpAddr(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
@ -492,6 +712,43 @@ void NpHandler::OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEv
}
ev.extdData = n.extdData; // extended-data (key,value) pairs -> dispatched as pExtdData
NpWebApi::EnqueuePushEvent(ev);
// Also surface a SESSION_INVITATION system-service event for titles that watch it instead of
// (or in addition to) the WebAPI push callback
if (n.npServiceName == "sessionInvitation") {
std::string session_id, invitation_id;
int64_t valid_until = 0;
for (const auto& kv : n.extdData) {
if (kv.first == "sessionId") {
session_id = kv.second;
} else if (kv.first == "invitationId") {
invitation_id = kv.second;
} else if (kv.first == "validUntil") {
valid_until = std::strtoll(kv.second.c_str(), nullptr, 10);
}
}
if (!session_id.empty()) {
{
std::lock_guard lk(m_mutex_pending_invites);
auto& v = m_pending_invites[user_id];
v.erase(std::remove_if(v.begin(), v.end(),
[&](const PendingInvitation& p) {
return p.invitation_id == invitation_id;
}),
v.end());
v.push_back({session_id, invitation_id, n.fromNpid, n.toNpid, valid_until});
}
// ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION is a *join* event: it
// fires only after the user explicitly accepts, via the game-opened invitation dialog
// (RECV) or the system software UI. Posting it here on arrival makes titles silently
// auto-join. The event is raised from AcceptSessionInvitation instead. The WebAPI push
// callback above still fires on arrival for titles that watch invites themselves.
//
// The emulator has no ShellUI, so surface the "system software UI" half here: an
// overlay prompt whose Accept routes through AcceptSessionInvitation.
ImGui::InvitationPrompt::Push(user_id, invitation_id, session_id, n.fromNpid);
}
}
}
void NpHandler::OnLoginResult(s32 user_id, const ShadNet::LoginResult& res) {
@ -1626,7 +1883,7 @@ static u32 FillRankArrayFromProto(const shadnet::GetScoreResponse& resp,
void NpHandler::OnAsyncReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
ShadNet::ErrorType error, const std::vector<u8>& body) {
const auto cmd_val = static_cast<u16>(cmd);
if (cmd_val >= 12 && cmd_val <= 23) {
if (cmd_val >= 12 && cmd_val <= 26) {
NpMatching2::OnMatchingReply(cmd, pkt_id, error, body);
} else {
OnScoreReply(user_id, cmd, pkt_id, error, body);

View File

@ -72,6 +72,33 @@ public:
// WebAPI bearer token
std::string GetBearerToken(s32 user_id) const;
// Sends a session invitation via the WebAPI (POST /v1/sessions/<id>/invitations). Each 'to'
// entry is an online ID (npid) or a decimal account ID
bool SendSessionInvitation(s32 user_id, const std::string& session_id,
const std::vector<std::string>& to, const std::string& message);
// Raises ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION (0x10000002) so titles that watch the
// system-service event learn about the invite.
void PostSessionInvitationEvent(const std::string& session_id, const std::string& invitation_id,
const std::string& accepter_online_id);
// A session invitation surfaced from a shadNet push, stashed until the user acts on it.
struct PendingInvitation {
std::string session_id;
std::string invitation_id;
std::string from_npid; // sender (for display)
std::string to_npid; // local recipient / accepter
int64_t valid_until = 0; // ms since epoch; 0 = never expires
};
// Pending invitations stashed for a local user (newest last), populated on arrival.
std::vector<PendingInvitation> GetPendingInvitations(s32 user_id) const;
// Consumes an invitation server-side (PUT usedFlag=true) and drops it from the stash. Returns
// true if the invite was found and the consume succeeded.
bool AcceptSessionInvitation(s32 user_id, const std::string& invitation_id);
// Dismisses an invitation locally by dropping it from the stash. No server call (a declined
// invite is left unconsumed and simply ages out server-side).
void DeclineSessionInvitation(s32 user_id, const std::string& invitation_id);
// Local IP address (network byte order) as seen at connect time.
u32 GetLocalIpAddr(s32 user_id) const;
@ -277,6 +304,9 @@ private:
};
std::unordered_map<s32, ReconnectState> m_reconnect;
mutable std::mutex m_mutex_pending_invites;
std::unordered_map<s32, std::vector<PendingInvitation>> m_pending_invites;
// State callbacks
struct CbEntry {
s32 handle;

View File

@ -15,7 +15,6 @@
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
#include "core/libraries/np/np_matching2/np_matching2_types.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
@ -55,7 +54,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex
const Libraries::Np::OrbisNpId np_id =
Libraries::Np::NpHandler::GetInstance().GetNpId(param->userId);
return ContextManager::Instance().CreateContext(&np_id, param->serviceLabel, ctxId);
return ContextManager::Instance().CreateContext(&np_id, param->serviceLabel, ctxId, true);
}
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId,
@ -416,7 +415,7 @@ int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId,
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmSearchRoom(ctxId, reqId, *request);
MmSearchRoom(ctxId, reqId, *request, ctx->a_variant);
return ORBIS_OK;
}
@ -442,8 +441,9 @@ int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId,
}
StoreRequestCallback(ctx, requestOpt);
*requestId = AllocRequestId();
LOG_WARNING(Lib_NpMatching2, "not implemented");
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmSetUserInfo(ctxId, reqId, *request);
return ORBIS_OK;
}
@ -690,8 +690,30 @@ int PS4_SYSV_ABI sceNpMatching2GetLobbyMemberDataInternalList() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2GetRoomDataExternalList() {
LOG_INFO(Lib_NpMatching2, "called");
int PS4_SYSV_ABI sceNpMatching2GetRoomDataExternalList(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomDataExternalListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmGetRoomDataExternalList(ctxId, reqId, *request, ctx->a_variant);
return ORBIS_OK;
}
@ -735,8 +757,30 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList() {
LOG_INFO(Lib_NpMatching2, "called");
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomMemberDataExternalListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmGetRoomMemberDataExternalList(ctxId, reqId, *request, ctx->a_variant);
return ORBIS_OK;
}
@ -745,13 +789,59 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2GetUserInfoListA() {
LOG_INFO(Lib_NpMatching2, "called");
int PS4_SYSV_ABI sceNpMatching2GetUserInfoListA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2GetUserInfoListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmGetUserInfoList(ctxId, reqId, *request, true);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2GetUserInfoList() {
LOG_INFO(Lib_NpMatching2, "called");
int PS4_SYSV_ABI sceNpMatching2GetUserInfoList(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2GetUserInfoListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmGetUserInfoList(ctxId, reqId, *request, ctx->a_variant);
return ORBIS_OK;
}

View File

@ -4,6 +4,7 @@
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types2.h"
namespace Core::Loader {
class SymbolsResolver;
@ -11,142 +12,6 @@ class SymbolsResolver;
namespace Libraries::Np::NpMatching2 {
using OrbisNpMatching2AttributeId = u16;
using OrbisNpMatching2BlockKickFlag = u8;
using OrbisNpMatching2ContextId = u16;
using OrbisNpMatching2FlagAttr = u32;
using OrbisNpMatching2Flags = u32;
using OrbisNpMatching2LobbyId = u64;
using OrbisNpMatching2LobbyMemberId = u16;
using OrbisNpMatching2NatType = u8;
using OrbisNpMatching2RequestId = u32;
using OrbisNpMatching2RoomGroupId = u8;
using OrbisNpMatching2RoomId = u64;
using OrbisNpMatching2RoomMemberId = u16;
using OrbisNpMatching2RoomPasswordSlotMask = u64;
using OrbisNpMatching2ServerId = u16;
using OrbisNpMatching2SignalingFlag = u8;
using OrbisNpMatching2SignalingRequestId = u32;
using OrbisNpMatching2TeamId = u8;
using OrbisNpMatching2WorldId = u32;
enum OrbisNpMatching2Event : u16 {
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST = 0x0002,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL = 0x0004,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO = 0x0007,
ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM = 0x0101,
ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM = 0x0102,
ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM = 0x0103,
ORBIS_NP_MATCHING2_REQUEST_EVENT_KICKOUT_ROOM_MEMBER = 0x0104,
ORBIS_NP_MATCHING2_REQUEST_EVENT_GRANT_ROOM_OWNER = 0x0105,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM = 0x0106,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE = 0x0108,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL = 0x0109,
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_INTERNAL = 0x010A,
ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_LOBBY = 0x0201,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO = 0x0E01,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED = 0x1101,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT = 0x1102,
ORBIS_NP_MATCHING2_ROOM_EVENT_KICKEDOUT = 0x1103,
ORBIS_NP_MATCHING2_ROOM_EVENT_ROOM_DESTROYED = 0x1104,
ORBIS_NP_MATCHING2_ROOM_EVENT_ROOM_OWNER_CHANGED = 0x1105,
ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_DATA_INTERNAL = 0x1106,
ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL = 0x1107,
ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_SIGNALING_OPT_PARAM = 0x1108,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD = 0x5101,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED = 0x5102,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_NETINFO_ERROR = 0x5103,
ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER = 0x6F01,
ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED = 0x6F02,
ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED = 0x6F03,
ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A = 0x7101,
ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A = 0x7102,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED_A = 0x8101,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT_A = 0x8102,
ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL_A = 0x8107,
ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE_A = 0x9102,
};
enum OrbisNpMatching2EventCause : u8 {
ORBIS_NP_MATCHING2_EVENT_CAUSE_LEAVE_ACTION = 1,
ORBIS_NP_MATCHING2_EVENT_CAUSE_KICKOUT_ACTION = 2,
ORBIS_NP_MATCHING2_EVENT_CAUSE_GRANT_OWNER_ACTION = 3,
ORBIS_NP_MATCHING2_EVENT_CAUSE_SERVER_OPERATION = 4,
ORBIS_NP_MATCHING2_EVENT_CAUSE_MEMBER_DISAPPEARED = 5,
ORBIS_NP_MATCHING2_EVENT_CAUSE_SERVER_INTERNAL = 6,
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONNECTION_ERROR = 7,
ORBIS_NP_MATCHING2_EVENT_CAUSE_NP_SIGNED_OUT = 8,
ORBIS_NP_MATCHING2_EVENT_CAUSE_SYSTEM_ERROR = 9,
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR = 10,
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION = 11,
};
enum OrbisNpMatching2SignalingType : u8 {
ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE = 0,
ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH = 1,
ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR = 2,
};
enum OrbisNpMatching2SignalingConnectionInfoType : s32 {
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT = 1,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH = 2,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NP_ID = 3,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR = 4,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR = 5,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS = 6,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NPID = 7,
ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDRESS_A = 7,
};
enum OrbisNpMatching2AttrId : OrbisNpMatching2AttributeId {
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID = 0x004C,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_2_ID = 0x004D,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_3_ID = 0x004E,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_4_ID = 0x004F,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_5_ID = 0x0050,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_6_ID = 0x0051,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_7_ID = 0x0052,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_8_ID = 0x0053,
ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_BIN_ATTR_EXTERNAL_1_ID = 0x0054,
ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_EXTERNAL_1_ID = 0x0055,
ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_EXTERNAL_2_ID = 0x0056,
ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_INTERNAL_1_ID = 0x0057,
ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_INTERNAL_2_ID = 0x0058,
ORBIS_NP_MATCHING2_ROOMMEMBER_BIN_ATTR_INTERNAL_1_ID = 0x0059,
};
enum OrbisNpMatching2RoomFlagAttr : OrbisNpMatching2Flags {
ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_OWNER_AUTO_GRANT = 0x80000000,
ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_CLOSED = 0x40000000,
ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_FULL = 0x20000000,
ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_HIDDEN = 0x10000000,
ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_NAT_TYPE_RESTRICTION = 0x04000000,
ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_PROHIBITIVE_MODE = 0x02000000,
};
enum OrbisNpMatching2RoomMemberFlagAttr : OrbisNpMatching2Flags {
ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER = 0x80000000,
};
enum OrbisNpMatching2Role : u8 {
ORBIS_NP_MATCHING2_ROLE_MEMBER = 1,
ORBIS_NP_MATCHING2_ROLE_OWNER = 2,
};
constexpr u32 ORBIS_NP_MATCHING2_SESSION_PASSWORD_SIZE = 8;
constexpr u32 ORBIS_NP_MATCHING2_GROUP_LABEL_SIZE = 8;
enum OrbisNpMatching2InitDefault : u64 {
ORBIS_NP_MATCHING2_THREAD_STACK_SIZE_DEFAULT = 32768,
ORBIS_NP_MATCHING2_POOLSIZE_DEFAULT = 131072,
ORBIS_NP_MATCHING2_SSL_POOLSIZE_DEFAULT = 196608,
};
struct OrbisNpMatching2InitializeParameter;
struct OrbisNpMatching2CreateContextParameter;
struct OrbisNpMatching2CreateContextParameterA;
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param);
int PS4_SYSV_ABI sceNpMatching2Terminate();
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,

View File

@ -28,6 +28,34 @@ u32 IpStringToAddr(std::string_view ip) {
return a | (b << 8) | (c << 16) | (d << 24);
}
template <typename Reply>
void ReserveExternalRoomPayloadStorage(CallbackPayload& p, const Reply& resp) {
size_t groups = 0;
size_t int_attrs = 0;
size_t bin_attrs = 0;
size_t bin_buffers = 0;
size_t owners = 0;
for (int i = 0; i < resp.rooms_size(); ++i) {
const auto& r = resp.rooms(i);
groups += static_cast<size_t>(r.groups_size());
int_attrs += static_cast<size_t>(r.external_search_int_attrs_size());
const size_t room_bin_attrs = static_cast<size_t>(r.external_search_bin_attrs_size()) +
static_cast<size_t>(r.external_bin_attrs_size());
bin_attrs += room_bin_attrs;
bin_buffers += room_bin_attrs;
if (!r.owner_npid().empty()) {
++owners;
}
}
p.ext_room_groups.reserve(groups);
p.ext_int_attrs.reserve(int_attrs);
p.ext_bin_attrs.reserve(bin_attrs);
p.bin_buffers.reserve(bin_buffers);
p.ext_owner_npids.reserve(owners);
}
} // namespace
void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx,
@ -343,6 +371,7 @@ void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldIn
void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
const int room_count = resp.rooms_size();
p.room_data_external.resize(room_count);
@ -444,6 +473,483 @@ void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply&
return p.request_data;
}
void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
const int room_count = resp.rooms_size();
p.room_data_external_a.resize(room_count);
for (int i = 0; i < room_count; ++i) {
const auto& r = resp.rooms(i);
auto& dst = p.room_data_external_a[i];
dst = OrbisNpMatching2RoomDataExternalA{};
dst.next = (i + 1 < room_count) ? &p.room_data_external_a[i + 1] : nullptr;
dst.maxSlot = static_cast<u16>(r.max_slot());
dst.curMembers = static_cast<u16>(r.cur_members());
dst.flags = r.flags();
dst.serverId = static_cast<OrbisNpMatching2ServerId>(r.server_id());
dst.worldId = static_cast<OrbisNpMatching2WorldId>(r.world_id());
dst.lobbyId = r.lobby_id();
dst.roomId = r.room_id();
dst.passwdSlotMask = r.passwd_slot_mask();
dst.joinedSlotMask = r.joined_slot_mask();
dst.publicSlots = static_cast<u16>(r.public_slots());
dst.privateSlots = static_cast<u16>(r.private_slots());
dst.openPublicSlots = static_cast<u16>(r.open_public_slots());
dst.openPrivateSlots = static_cast<u16>(r.open_private_slots());
if (!r.owner_npid().empty()) {
std::strncpy(dst.ownerOnlineId.data, r.owner_npid().c_str(),
sizeof(dst.ownerOnlineId.data) - 1);
}
dst.owner.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(r.owner_account_id());
dst.owner.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(r.owner_platform());
if (r.groups_size() > 0) {
OrbisNpMatching2RoomGroupInfo* first = nullptr;
for (int g = 0; g < r.groups_size(); ++g) {
const auto& src = r.groups(g);
auto& gi = p.ext_room_groups.emplace_back();
gi = OrbisNpMatching2RoomGroupInfo{};
gi.id = static_cast<OrbisNpMatching2RoomGroupId>(src.group_id());
gi.hasPasswd = src.has_passwd();
gi.slots = src.slot_count();
gi.groupMembers = src.num_members();
if (!first) {
first = &gi;
}
}
dst.roomGroup = first;
dst.roomGroups = static_cast<u64>(r.groups_size());
}
if (r.external_search_int_attrs_size() > 0) {
OrbisNpMatching2IntAttr* first = nullptr;
for (int a = 0; a < r.external_search_int_attrs_size(); ++a) {
const auto& src = r.external_search_int_attrs(a);
auto& ia = p.ext_int_attrs.emplace_back();
ia = OrbisNpMatching2IntAttr{};
ia.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ia.num = src.attr_value();
if (!first) {
first = &ia;
}
}
dst.externalSearchIntAttr = first;
dst.externalSearchIntAttrs = static_cast<u64>(r.external_search_int_attrs_size());
}
auto build_bin = [&](const auto& src_attrs, OrbisNpMatching2BinAttr*& out_ptr,
u64& out_num) {
if (src_attrs.empty()) {
return;
}
OrbisNpMatching2BinAttr* first = nullptr;
for (const auto& src : src_attrs) {
p.bin_buffers.emplace_back(src.data().begin(), src.data().end());
auto& buf = p.bin_buffers.back();
auto& ba = p.ext_bin_attrs.emplace_back();
ba = OrbisNpMatching2BinAttr{};
ba.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ba.data = buf.empty() ? nullptr : buf.data();
ba.dataSize = buf.size();
if (!first) {
first = &ba;
}
}
out_ptr = first;
out_num = static_cast<u64>(src_attrs.size());
};
build_bin(r.external_search_bin_attrs(), dst.externalSearchBinAttr,
dst.externalSearchBinAttrs);
build_bin(r.external_bin_attrs(), dst.externalBinAttr, dst.externalBinAttrs);
}
p.search_room_response_a = std::make_unique<OrbisNpMatching2SearchRoomResponseA>();
auto& out = *p.search_room_response_a;
out = OrbisNpMatching2SearchRoomResponseA{};
out.range.start = resp.range_start();
out.range.total = resp.range_total();
out.range.results = resp.range_result();
out.roomDataExt = p.room_data_external_a.empty() ? nullptr : p.room_data_external_a.data();
p.request_data = p.search_room_response_a.get();
return p.request_data;
}
void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
const int room_count = resp.rooms_size();
p.room_data_external.resize(room_count);
for (int i = 0; i < room_count; ++i) {
const auto& r = resp.rooms(i);
auto& dst = p.room_data_external[i];
dst = OrbisNpMatching2RoomDataExternal{};
dst.next = (i + 1 < room_count) ? &p.room_data_external[i + 1] : nullptr;
dst.maxSlot = static_cast<u16>(r.max_slot());
dst.curMembers = static_cast<u16>(r.cur_members());
dst.flags = r.flags();
dst.serverId = static_cast<OrbisNpMatching2ServerId>(r.server_id());
dst.worldId = static_cast<OrbisNpMatching2WorldId>(r.world_id());
dst.lobbyId = r.lobby_id();
dst.roomId = r.room_id();
dst.passwdSlotMask = r.passwd_slot_mask();
dst.joinedSlotMask = r.joined_slot_mask();
dst.publicSlots = static_cast<u16>(r.public_slots());
dst.privateSlots = static_cast<u16>(r.private_slots());
dst.openPublicSlots = static_cast<u16>(r.open_public_slots());
dst.openPrivateSlots = static_cast<u16>(r.open_private_slots());
if (!r.owner_npid().empty()) {
auto& npid = p.ext_owner_npids.emplace_back();
npid = Libraries::Np::OrbisNpId{};
std::strncpy(npid.handle.data, r.owner_npid().c_str(), sizeof(npid.handle.data) - 1);
dst.ownerNpId = &npid;
}
if (r.groups_size() > 0) {
OrbisNpMatching2RoomGroupInfo* first = nullptr;
for (int g = 0; g < r.groups_size(); ++g) {
const auto& src = r.groups(g);
auto& gi = p.ext_room_groups.emplace_back();
gi = OrbisNpMatching2RoomGroupInfo{};
gi.id = static_cast<OrbisNpMatching2RoomGroupId>(src.group_id());
gi.hasPasswd = src.has_passwd();
gi.slots = src.slot_count();
gi.groupMembers = src.num_members();
if (!first) {
first = &gi;
}
}
dst.roomGroup = first;
dst.roomGroups = static_cast<u64>(r.groups_size());
}
if (r.external_search_int_attrs_size() > 0) {
OrbisNpMatching2IntAttr* first = nullptr;
for (int a = 0; a < r.external_search_int_attrs_size(); ++a) {
const auto& src = r.external_search_int_attrs(a);
auto& ia = p.ext_int_attrs.emplace_back();
ia = OrbisNpMatching2IntAttr{};
ia.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ia.num = src.attr_value();
if (!first) {
first = &ia;
}
}
dst.externalSearchIntAttr = first;
dst.externalSearchIntAttrs = static_cast<u64>(r.external_search_int_attrs_size());
}
auto build_bin = [&](const auto& src_attrs, OrbisNpMatching2BinAttr*& out_ptr,
u64& out_num) {
if (src_attrs.empty()) {
return;
}
OrbisNpMatching2BinAttr* first = nullptr;
for (const auto& src : src_attrs) {
p.bin_buffers.emplace_back(src.data().begin(), src.data().end());
auto& buf = p.bin_buffers.back();
auto& ba = p.ext_bin_attrs.emplace_back();
ba = OrbisNpMatching2BinAttr{};
ba.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ba.data = buf.empty() ? nullptr : buf.data();
ba.dataSize = buf.size();
if (!first) {
first = &ba;
}
}
out_ptr = first;
out_num = static_cast<u64>(src_attrs.size());
};
build_bin(r.external_search_bin_attrs(), dst.externalSearchBinAttr,
dst.externalSearchBinAttrs);
build_bin(r.external_bin_attrs(), dst.externalBinAttr, dst.externalBinAttrs);
}
p.room_data_external_list_response =
std::make_unique<OrbisNpMatching2GetRoomDataExternalListResponse>();
auto& out = *p.room_data_external_list_response;
out = OrbisNpMatching2GetRoomDataExternalListResponse{};
out.roomDataExternal = p.room_data_external.empty() ? nullptr : p.room_data_external.data();
out.roomDataExternalNum = static_cast<u64>(p.room_data_external.size());
p.request_data = p.room_data_external_list_response.get();
return p.request_data;
}
void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
const int room_count = resp.rooms_size();
p.room_data_external_a.resize(room_count);
for (int i = 0; i < room_count; ++i) {
const auto& r = resp.rooms(i);
auto& dst = p.room_data_external_a[i];
dst = OrbisNpMatching2RoomDataExternalA{};
dst.next = (i + 1 < room_count) ? &p.room_data_external_a[i + 1] : nullptr;
dst.maxSlot = static_cast<u16>(r.max_slot());
dst.curMembers = static_cast<u16>(r.cur_members());
dst.flags = r.flags();
dst.serverId = static_cast<OrbisNpMatching2ServerId>(r.server_id());
dst.worldId = static_cast<OrbisNpMatching2WorldId>(r.world_id());
dst.lobbyId = r.lobby_id();
dst.roomId = r.room_id();
dst.passwdSlotMask = r.passwd_slot_mask();
dst.joinedSlotMask = r.joined_slot_mask();
dst.publicSlots = static_cast<u16>(r.public_slots());
dst.privateSlots = static_cast<u16>(r.private_slots());
dst.openPublicSlots = static_cast<u16>(r.open_public_slots());
dst.openPrivateSlots = static_cast<u16>(r.open_private_slots());
if (!r.owner_npid().empty()) {
std::strncpy(dst.ownerOnlineId.data, r.owner_npid().c_str(),
sizeof(dst.ownerOnlineId.data) - 1);
}
dst.owner.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(r.owner_account_id());
dst.owner.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(r.owner_platform());
if (r.groups_size() > 0) {
OrbisNpMatching2RoomGroupInfo* first = nullptr;
for (int g = 0; g < r.groups_size(); ++g) {
const auto& src = r.groups(g);
auto& gi = p.ext_room_groups.emplace_back();
gi = OrbisNpMatching2RoomGroupInfo{};
gi.id = static_cast<OrbisNpMatching2RoomGroupId>(src.group_id());
gi.hasPasswd = src.has_passwd();
gi.slots = src.slot_count();
gi.groupMembers = src.num_members();
if (!first) {
first = &gi;
}
}
dst.roomGroup = first;
dst.roomGroups = static_cast<u64>(r.groups_size());
}
if (r.external_search_int_attrs_size() > 0) {
OrbisNpMatching2IntAttr* first = nullptr;
for (int a = 0; a < r.external_search_int_attrs_size(); ++a) {
const auto& src = r.external_search_int_attrs(a);
auto& ia = p.ext_int_attrs.emplace_back();
ia = OrbisNpMatching2IntAttr{};
ia.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ia.num = src.attr_value();
if (!first) {
first = &ia;
}
}
dst.externalSearchIntAttr = first;
dst.externalSearchIntAttrs = static_cast<u64>(r.external_search_int_attrs_size());
}
auto build_bin = [&](const auto& src_attrs, OrbisNpMatching2BinAttr*& out_ptr,
u64& out_num) {
if (src_attrs.empty()) {
return;
}
OrbisNpMatching2BinAttr* first = nullptr;
for (const auto& src : src_attrs) {
p.bin_buffers.emplace_back(src.data().begin(), src.data().end());
auto& buf = p.bin_buffers.back();
auto& ba = p.ext_bin_attrs.emplace_back();
ba = OrbisNpMatching2BinAttr{};
ba.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ba.data = buf.empty() ? nullptr : buf.data();
ba.dataSize = buf.size();
if (!first) {
first = &ba;
}
}
out_ptr = first;
out_num = static_cast<u64>(src_attrs.size());
};
build_bin(r.external_search_bin_attrs(), dst.externalSearchBinAttr,
dst.externalSearchBinAttrs);
build_bin(r.external_bin_attrs(), dst.externalBinAttr, dst.externalBinAttrs);
}
p.room_data_external_list_response_a =
std::make_unique<OrbisNpMatching2GetRoomDataExternalListResponseA>();
auto& out = *p.room_data_external_list_response_a;
out = OrbisNpMatching2GetRoomDataExternalListResponseA{};
out.roomDataExternal = p.room_data_external_a.empty() ? nullptr : p.room_data_external_a.data();
out.roomDataExternalNum = static_cast<u64>(p.room_data_external_a.size());
p.request_data = p.room_data_external_list_response_a.get();
return p.request_data;
}
void* BuildGetRoomMemberDataExternalListPayload(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
const int member_count = resp.members_size();
p.member_data_external.resize(member_count);
for (int i = 0; i < member_count; ++i) {
const auto& src = resp.members(i);
auto& dst = p.member_data_external[i];
dst = OrbisNpMatching2RoomMemberDataExternal{};
dst.next = (i + 1 < member_count) ? &p.member_data_external[i + 1] : nullptr;
std::strncpy(dst.npId.handle.data, src.npid().c_str(), sizeof(dst.npId.handle.data) - 1);
dst.joinDate.tick = src.join_date();
dst.role = static_cast<OrbisNpMatching2Role>(src.role());
}
p.room_member_data_external_list_response =
std::make_unique<OrbisNpMatching2GetRoomMemberDataExternalListResponse>();
auto& out = *p.room_member_data_external_list_response;
out = OrbisNpMatching2GetRoomMemberDataExternalListResponse{};
out.roomMemberDataExternal =
p.member_data_external.empty() ? nullptr : p.member_data_external.data();
out.roomMemberDataExternalNum = static_cast<u64>(p.member_data_external.size());
p.request_data = p.room_member_data_external_list_response.get();
return p.request_data;
}
void* BuildGetRoomMemberDataExternalListPayloadA(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
const int member_count = resp.members_size();
p.member_data_external_a.resize(member_count);
for (int i = 0; i < member_count; ++i) {
const auto& src = resp.members(i);
auto& dst = p.member_data_external_a[i];
dst = OrbisNpMatching2RoomMemberDataExternalA{};
dst.next = (i + 1 < member_count) ? &p.member_data_external_a[i + 1] : nullptr;
dst.user.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(src.account_id());
dst.user.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(src.platform());
std::strncpy(dst.onlineId.data, src.npid().c_str(), sizeof(dst.onlineId.data) - 1);
dst.joinDate.tick = src.join_date();
dst.role = static_cast<OrbisNpMatching2Role>(src.role());
}
p.room_member_data_external_list_response_a =
std::make_unique<OrbisNpMatching2GetRoomMemberDataExternalListResponseA>();
auto& out = *p.room_member_data_external_list_response_a;
out = OrbisNpMatching2GetRoomMemberDataExternalListResponseA{};
out.roomMemberDataExternal =
p.member_data_external_a.empty() ? nullptr : p.member_data_external_a.data();
out.roomMemberDataExternalNum = static_cast<u64>(p.member_data_external_a.size());
p.request_data = p.room_member_data_external_list_response_a.get();
return p.request_data;
}
void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
size_t total_bin_attrs = 0;
for (int i = 0; i < resp.users_size(); ++i) {
total_bin_attrs += static_cast<size_t>(resp.users(i).user_bin_attrs_size());
}
p.user_bin_attrs.reserve(total_bin_attrs);
p.bin_buffers.reserve(total_bin_attrs);
const int user_count = resp.users_size();
p.user_info.resize(user_count);
for (int i = 0; i < user_count; ++i) {
const auto& u = resp.users(i);
auto& dst = p.user_info[i];
dst = OrbisNpMatching2UserInfo{};
dst.next = (i + 1 < user_count) ? &p.user_info[i + 1] : nullptr;
std::strncpy(dst.npId.handle.data, u.npid().c_str(), sizeof(dst.npId.handle.data) - 1);
if (u.user_bin_attrs_size() > 0) {
OrbisNpMatching2BinAttr* first = nullptr;
for (int a = 0; a < u.user_bin_attrs_size(); ++a) {
const auto& src = u.user_bin_attrs(a);
p.bin_buffers.emplace_back(src.data().begin(), src.data().end());
auto& buf = p.bin_buffers.back();
auto& ba = p.user_bin_attrs.emplace_back();
ba = OrbisNpMatching2BinAttr{};
ba.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ba.data = buf.empty() ? nullptr : buf.data();
ba.dataSize = buf.size();
if (!first) {
first = &ba;
}
}
dst.userBinAttr = first;
dst.userBinAttrNum = static_cast<u64>(u.user_bin_attrs_size());
}
}
p.user_info_list_response = std::make_unique<OrbisNpMatching2GetUserInfoListResponse>();
auto& out = *p.user_info_list_response;
out = OrbisNpMatching2GetUserInfoListResponse{};
out.userInfo = p.user_info.empty() ? nullptr : p.user_info.data();
out.userInfoNum = static_cast<u64>(p.user_info.size());
p.request_data = p.user_info_list_response.get();
return p.request_data;
}
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
size_t total_bin_attrs = 0;
for (int i = 0; i < resp.users_size(); ++i) {
total_bin_attrs += static_cast<size_t>(resp.users(i).user_bin_attrs_size());
}
p.user_bin_attrs.reserve(total_bin_attrs);
p.bin_buffers.reserve(total_bin_attrs);
const int user_count = resp.users_size();
p.user_info_a.resize(user_count);
for (int i = 0; i < user_count; ++i) {
const auto& u = resp.users(i);
auto& dst = p.user_info_a[i];
dst = OrbisNpMatching2UserInfoA{};
dst.next = (i + 1 < user_count) ? &p.user_info_a[i + 1] : nullptr;
std::strncpy(dst.userOnlineId.data, u.npid().c_str(), sizeof(dst.userOnlineId.data) - 1);
dst.user.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(u.account_id());
dst.user.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(u.platform());
if (u.user_bin_attrs_size() > 0) {
OrbisNpMatching2BinAttr* first = nullptr;
for (int a = 0; a < u.user_bin_attrs_size(); ++a) {
const auto& src = u.user_bin_attrs(a);
p.bin_buffers.emplace_back(src.data().begin(), src.data().end());
auto& buf = p.bin_buffers.back();
auto& ba = p.user_bin_attrs.emplace_back();
ba = OrbisNpMatching2BinAttr{};
ba.id = static_cast<OrbisNpMatching2AttributeId>(src.attr_id());
ba.data = buf.empty() ? nullptr : buf.data();
ba.dataSize = buf.size();
if (!first) {
first = &ba;
}
}
dst.userBinAttr = first;
dst.userBinAttrNum = static_cast<u64>(u.user_bin_attrs_size());
}
}
p.user_info_list_response_a = std::make_unique<OrbisNpMatching2GetUserInfoListResponseA>();
auto& out = *p.user_info_list_response_a;
out = OrbisNpMatching2GetUserInfoListResponseA{};
out.userInfo = p.user_info_a.empty() ? nullptr : p.user_info_a.data();
out.userInfoNum = static_cast<u64>(p.user_info_a.size());
p.request_data = p.user_info_list_response_a.get();
return p.request_data;
}
void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id) {
const auto rc_it = ctx.room_cache.find(room_id);
if (rc_it == ctx.room_cache.end()) {
@ -548,7 +1054,7 @@ ContextObject* ContextManager::GetLocked(OrbisNpMatching2ContextId ctx_id) {
}
s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLabel service_label,
OrbisNpMatching2ContextId* out_ctx_id) {
OrbisNpMatching2ContextId* out_ctx_id, bool a_variant) {
if (!out_ctx_id) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
@ -572,6 +1078,7 @@ s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLa
ContextObject& ctx = m_contexts[id];
ctx.Reset();
ctx.ctx_id = id;
ctx.a_variant = a_variant;
ctx.service_label = service_label;
if (owner_np_id) {
ctx.owner_np_id = *owner_np_id;
@ -582,8 +1089,8 @@ s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLa
m_used[id] = true;
*out_ctx_id = id;
LOG_DEBUG(Lib_NpMatching2, "context created: id={} online_id={} serviceLabel={:#x}", id,
ctx.online_id.data, service_label);
LOG_DEBUG(Lib_NpMatching2, "context{} created: id={} online_id={} serviceLabel={:#x}",
a_variant ? "A" : "", id, ctx.online_id.data, service_label);
return ORBIS_OK;
}

View File

@ -7,7 +7,6 @@
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
@ -17,12 +16,14 @@
#include "common/types.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_matching2/np_matching2_types.h"
#include "core/libraries/np/np_types.h"
namespace shadnet {
class CreateJoinRoomResponse;
class GetWorldInfoListReply;
class GetRoomDataExternalListReply;
class GetRoomMemberDataExternalListReply;
class GetUserInfoListReply;
class LeaveRoomReply;
class SearchRoomReply;
} // namespace shadnet
@ -96,25 +97,43 @@ struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponseA> create_join_response_a;
std::unique_ptr<OrbisNpMatching2LeaveRoomResponse> leave_room_response;
std::unique_ptr<OrbisNpMatching2SearchRoomResponse> search_room_response;
std::unique_ptr<OrbisNpMatching2SearchRoomResponseA> search_room_response_a;
std::unique_ptr<OrbisNpMatching2GetRoomDataExternalListResponse>
room_data_external_list_response;
std::unique_ptr<OrbisNpMatching2GetRoomDataExternalListResponseA>
room_data_external_list_response_a;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataExternalListResponse>
room_member_data_external_list_response;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataExternalListResponseA>
room_member_data_external_list_response_a;
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponse> user_info_list_response;
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponseA> user_info_list_response_a;
std::unique_ptr<OrbisNpMatching2GetWorldInfoListResponse> world_info_response;
std::unique_ptr<OrbisNpMatching2SignalingGetPingInfoResponse> ping_info_response;
std::vector<OrbisNpMatching2World> world_list;
std::vector<OrbisNpMatching2RoomMemberDataInternal> member_data;
std::vector<OrbisNpMatching2RoomMemberDataInternalA> member_data_a;
std::vector<OrbisNpMatching2RoomMemberDataExternal> member_data_external;
std::vector<OrbisNpMatching2RoomMemberDataExternalA> member_data_external_a;
std::vector<OrbisNpMatching2RoomGroup> room_groups;
std::vector<OrbisNpMatching2RoomGroup*> room_group_ptrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal> room_bin_attrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal*> room_bin_attr_ptrs;
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal> member_bin_attrs;
std::vector<OrbisNpMatching2RoomDataExternal> room_data_external;
std::vector<OrbisNpMatching2RoomDataExternalA> room_data_external_a;
std::vector<OrbisNpMatching2UserInfo> user_info;
std::vector<OrbisNpMatching2UserInfoA> user_info_a;
std::vector<OrbisNpMatching2BinAttr> user_bin_attrs;
std::vector<std::vector<u8>> bin_buffers;
std::deque<OrbisNpMatching2IntAttr> ext_int_attrs;
std::deque<OrbisNpMatching2BinAttr> ext_bin_attrs;
std::deque<OrbisNpMatching2RoomGroupInfo> ext_room_groups;
std::deque<Libraries::Np::OrbisNpId> ext_owner_npids;
std::vector<OrbisNpMatching2IntAttr> ext_int_attrs;
std::vector<OrbisNpMatching2BinAttr> ext_bin_attrs;
std::vector<OrbisNpMatching2RoomGroupInfo> ext_room_groups;
std::vector<Libraries::Np::OrbisNpId> ext_owner_npids;
void* request_data = nullptr;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdate> room_member_update;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdateA> room_member_update_a;
std::unique_ptr<OrbisNpMatching2RoomUpdate> room_update;
std::unique_ptr<OrbisNpMatching2RoomDataInternalUpdate> room_data_internal_update;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_chg_flag_attr;
@ -122,6 +141,7 @@ struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_chg_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_prev_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternal> event_member;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternalA> event_member_a;
void* room_event_data = nullptr;
CallbackPayload() = default;
@ -136,17 +156,30 @@ struct CallbackPayload {
create_join_response_a.reset();
leave_room_response.reset();
search_room_response.reset();
search_room_response_a.reset();
room_data_external_list_response.reset();
room_data_external_list_response_a.reset();
room_member_data_external_list_response.reset();
room_member_data_external_list_response_a.reset();
user_info_list_response.reset();
user_info_list_response_a.reset();
world_info_response.reset();
ping_info_response.reset();
world_list.clear();
member_data.clear();
member_data_a.clear();
member_data_external.clear();
member_data_external_a.clear();
room_groups.clear();
room_group_ptrs.clear();
room_bin_attrs.clear();
room_bin_attr_ptrs.clear();
member_bin_attrs.clear();
room_data_external.clear();
room_data_external_a.clear();
user_info.clear();
user_info_a.clear();
user_bin_attrs.clear();
bin_buffers.clear();
ext_int_attrs.clear();
ext_bin_attrs.clear();
@ -154,6 +187,7 @@ struct CallbackPayload {
ext_owner_npids.clear();
request_data = nullptr;
room_member_update.reset();
room_member_update_a.reset();
room_update.reset();
room_data_internal_update.reset();
event_chg_flag_attr.reset();
@ -161,6 +195,7 @@ struct CallbackPayload {
event_chg_passwd_slot_mask.reset();
event_prev_passwd_slot_mask.reset();
event_member.reset();
event_member_a.reset();
room_event_data = nullptr;
}
};
@ -168,6 +203,7 @@ struct CallbackPayload {
struct ContextObject {
OrbisNpMatching2ContextId ctx_id = 0;
bool started = false;
bool a_variant = false;
OrbisNpMatching2ServerId server_id = 1;
OrbisNpServiceLabel service_label = 0;
@ -214,6 +250,7 @@ struct ContextObject {
void Reset() {
ctx_id = 0;
started = false;
a_variant = false;
server_id = 1;
service_label = 0;
owner_np_id = {};
@ -253,12 +290,12 @@ struct ContextObject {
class ContextManager {
public:
static constexpr u32 kMaxContexts = 255;
static constexpr u32 kMaxContexts = 10;
static ContextManager& Instance();
s32 CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLabel service_label,
OrbisNpMatching2ContextId* out_ctx_id);
OrbisNpMatching2ContextId* out_ctx_id, bool a_variant = false);
bool Check(OrbisNpMatching2ContextId ctx_id);
ContextObject* Get(OrbisNpMatching2ContextId ctx_id);
@ -351,6 +388,17 @@ void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinR
void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp);
void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp);
void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp);
void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp);
void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp);
void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp);
void* BuildGetRoomMemberDataExternalListPayload(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetRoomMemberDataExternalListPayloadA(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
void InitEventDispatcher();

View File

@ -190,10 +190,35 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
if (reply.ParseFromString(proto)) {
request_data = BuildGetWorldInfoListPayload(*ctx, reply);
}
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM) {
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A) {
shadnet::SearchRoomReply reply;
if (reply.ParseFromString(proto)) {
request_data = BuildSearchRoomPayload(*ctx, reply);
request_data = pr.a_variant ? BuildSearchRoomPayloadA(*ctx, reply)
: BuildSearchRoomPayload(*ctx, reply);
}
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST_A) {
shadnet::GetRoomDataExternalListReply reply;
if (reply.ParseFromString(proto)) {
request_data = pr.a_variant ? BuildGetRoomDataExternalListPayloadA(*ctx, reply)
: BuildGetRoomDataExternalListPayload(*ctx, reply);
}
} else if (pr.req_event ==
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST ||
pr.req_event ==
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST_A) {
shadnet::GetRoomMemberDataExternalListReply reply;
if (reply.ParseFromString(proto)) {
request_data = pr.a_variant
? BuildGetRoomMemberDataExternalListPayloadA(*ctx, reply)
: BuildGetRoomMemberDataExternalListPayload(*ctx, reply);
}
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_USER_INFO_LIST) {
shadnet::GetUserInfoListReply reply;
if (reply.ParseFromString(proto)) {
request_data = pr.a_variant ? BuildGetUserInfoListPayloadA(*ctx, reply)
: BuildGetUserInfoListPayload(*ctx, reply);
}
}
}
@ -210,16 +235,70 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
ev.request_data = request_data;
ScheduleEvent(std::move(ev));
if (error_code == 0 && (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM ||
if (error_code == 0 && (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A)) {
StartMatching2SignalingForRoomPeers(*ctx, ctx->room_id);
} else if (error_code == 0 &&
(pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A)) {
StartMatching2SignalingForRoomPeers(*ctx, ctx->room_id);
}
}
void AppendEventMemberBinAttrs(CallbackPayload& p, const MemberCache& mc) {
p.member_bin_attrs.resize(mc.bins.size());
p.bin_buffers.reserve(mc.bins.size());
size_t i = 0;
for (const auto& [attr_id, bin] : mc.bins) {
p.bin_buffers.emplace_back(bin.data.begin(), bin.data.end());
auto& buf = p.bin_buffers.back();
auto& dst = p.member_bin_attrs[i++];
dst = OrbisNpMatching2RoomMemberBinAttrInternal{};
dst.lastUpdate.tick = bin.update_date;
dst.binAttr.id = bin.id;
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
}
void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCache& mc,
OrbisNpMatching2EventCause cause) {
OrbisNpMatching2EventCause cause, bool a_variant) {
if (mc.group_id != 0) {
auto grp_it = rc.groups.find(mc.group_id);
if (grp_it != rc.groups.end()) {
p.room_groups.resize(1);
p.room_groups[0] = grp_it->second;
}
}
AppendEventMemberBinAttrs(p, mc);
if (a_variant) {
p.event_member_a = std::make_unique<OrbisNpMatching2RoomMemberDataInternalA>();
OrbisNpMatching2RoomMemberDataInternalA& m = *p.event_member_a;
m = OrbisNpMatching2RoomMemberDataInternalA{};
m.memberId = mc.member_id;
m.teamId = mc.team_id;
m.natType = mc.nat_type;
m.flags = mc.flag_attr;
m.joinDateTicks.tick = mc.join_date;
m.user.accountId = mc.account_id;
m.user.platform = mc.platform;
std::strncpy(m.onlineId.data, mc.np_id.handle.data, sizeof(m.onlineId.data) - 1);
m.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data();
m.roomMemberInternalBinAttr =
p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data();
m.roomMemberInternalBinAttrs = p.member_bin_attrs.size();
p.room_member_update_a = std::make_unique<OrbisNpMatching2RoomMemberUpdateA>();
OrbisNpMatching2RoomMemberUpdateA& u = *p.room_member_update_a;
u = OrbisNpMatching2RoomMemberUpdateA{};
u.roomMemberDataInternal = p.event_member_a.get();
u.eventCause = cause;
p.room_event_data = p.room_member_update_a.get();
return;
}
p.event_member = std::make_unique<OrbisNpMatching2RoomMemberDataInternal>();
OrbisNpMatching2RoomMemberDataInternal& m = *p.event_member;
m = OrbisNpMatching2RoomMemberDataInternal{};
@ -229,28 +308,7 @@ void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCach
m.flagAttr = mc.flag_attr;
m.joinDate = mc.join_date;
m.npId = mc.np_id;
if (mc.group_id != 0) {
auto grp_it = rc.groups.find(mc.group_id);
if (grp_it != rc.groups.end()) {
p.room_groups.resize(1);
p.room_groups[0] = grp_it->second;
m.roomGroup = p.room_groups.data();
}
}
p.member_bin_attrs.resize(mc.bins.size());
p.bin_buffers.reserve(mc.bins.size());
size_t i = 0;
for (const auto& [attr_id, bin] : mc.bins) {
p.bin_buffers.emplace_back(bin.data.begin(), bin.data.end());
auto& buf = p.bin_buffers.back();
auto& dst = p.member_bin_attrs[i++];
dst = OrbisNpMatching2RoomMemberBinAttrInternal{};
dst.binAttr.id = bin.id;
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
m.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data();
m.roomMemberBinAttrInternal = p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data();
m.roomMemberBinAttrInternalNum = p.member_bin_attrs.size();
@ -308,7 +366,7 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
std::strncpy(pi.online_id.data, n.member_npid.c_str(), sizeof(pi.online_id.data) - 1);
ctx->peers[member_id] = pi;
BuildMemberUpdate(p, room_it->second, mc, cause);
BuildMemberUpdate(p, room_it->second, mc, cause, ctx->a_variant);
break;
}
case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT: {
@ -320,7 +378,7 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
if (mem_it == room_it->second.members.end()) {
return;
}
BuildMemberUpdate(p, room_it->second, mem_it->second, cause);
BuildMemberUpdate(p, room_it->second, mem_it->second, cause, ctx->a_variant);
QueueMatching2SignalingEvent(*ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_PEER);
@ -463,12 +521,29 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
LOG_DEBUG(Lib_NpMatching2, "RoomEvent ctx={} room={} event={:#x} cause={}", n.ctx_id, n.room_id,
n.event, n.event_cause);
OrbisNpMatching2Event fired_event = event;
if (ctx->a_variant) {
switch (event) {
case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED:
fired_event = ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED_A;
break;
case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT:
fired_event = ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT_A;
break;
case ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL:
fired_event = ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL_A;
break;
default:
break;
}
}
PendingEvent ev{};
ev.type = PendingEvent::ROOM_EVENT_CB;
ev.ctx_id = ctx->ctx_id;
ev.fire_at = std::chrono::steady_clock::now();
ev.room_id = room_id;
ev.room_event = event;
ev.room_event = fired_event;
ev.room_event_data = p.room_event_data;
ScheduleEvent(std::move(ev));
@ -780,7 +855,7 @@ s32 MmGetWorldInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2Request
}
s32 MmSearchRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SearchRoomRequest& request) {
const OrbisNpMatching2SearchRoomRequest& request, bool a_variant) {
shadnet::SearchRoomRequest req;
req.set_world_id(request.worldId);
req.set_lobby_id(request.lobbyId);
@ -814,8 +889,71 @@ s32 MmSearchRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req
for (u64 i = 0; i < request.attrs; ++i) {
req.add_attr_ids(request.attr[i]);
}
return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM,
MmCommand::SearchRoom, MakeProtoPayload(req));
return MmSubmitRequest(ctx_id, req_id,
a_variant ? ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A
: ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM,
MmCommand::SearchRoom, MakeProtoPayload(req), a_variant);
}
s32 MmGetRoomDataExternalList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetRoomDataExternalListRequest& request,
bool a_variant) {
shadnet::GetRoomDataExternalListRequest req;
for (u64 i = 0; i < request.roomIdNum; ++i) {
req.add_room_ids(request.roomId[i]);
}
for (u64 i = 0; i < request.attrIdNum; ++i) {
req.add_attr_ids(request.attrId[i]);
}
LOG_DEBUG(Lib_NpMatching2, "getRoomDataExternalList roomN={} attrN={}", request.roomIdNum,
request.attrIdNum);
return MmSubmitRequest(ctx_id, req_id,
a_variant
? ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST_A
: ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST,
MmCommand::GetRoomDataExternalList, MakeProtoPayload(req), a_variant);
}
s32 MmGetRoomMemberDataExternalList(
OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetRoomMemberDataExternalListRequest& request, bool a_variant) {
shadnet::GetRoomMemberDataExternalListRequest req;
req.set_room_id(request.roomId);
LOG_DEBUG(Lib_NpMatching2, "getRoomMemberDataExternalList room={}", request.roomId);
return MmSubmitRequest(
ctx_id, req_id,
a_variant ? ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST_A
: ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST,
MmCommand::GetRoomMemberDataExternalList, MakeProtoPayload(req), a_variant);
}
s32 MmGetUserInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetUserInfoListRequest& request, bool a_variant) {
shadnet::GetUserInfoListRequest req;
for (u64 i = 0; i < request.npIdNum; ++i) {
req.add_npids(request.npId[i].handle.data);
}
for (u64 i = 0; i < request.attrIdNum; ++i) {
req.add_attr_ids(request.attrId[i]);
}
req.set_option(request.option);
LOG_DEBUG(Lib_NpMatching2, "getUserInfoList npN={} attrN={}", request.npIdNum,
request.attrIdNum);
return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_USER_INFO_LIST,
MmCommand::GetUserInfoList, MakeProtoPayload(req), a_variant);
}
s32 MmSetUserInfo(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetUserInfoRequest& request) {
shadnet::SetUserInfoRequest req;
req.set_server_id(request.serverId);
for (u64 i = 0; request.userBinAttr && i < request.userBinAttrs; ++i) {
AppendBinAttr(req.add_user_bin_attrs(), request.userBinAttr[i]);
}
LOG_DEBUG(Lib_NpMatching2, "setUserInfo server={} binAttrs={}", request.serverId,
request.userBinAttrs);
return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO,
MmCommand::SetUserInfo, MakeProtoPayload(req));
}
s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,

View File

@ -9,7 +9,6 @@
#include "common/types.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_matching2/np_matching2_types.h"
namespace ShadNet {
class ShadNetClient;
@ -27,10 +26,14 @@ enum class MmCommand : u16 {
SearchRoom = 16,
RequestSignalingInfos = 17,
ContextStop = 18,
SetUserInfo = 19,
SetRoomDataInternal = 20,
SetRoomDataExternal = 21,
KickoutRoomMember = 22,
GetWorldInfoList = 23,
GetRoomDataExternalList = 24,
GetUserInfoList = 25,
GetRoomMemberDataExternalList = 26,
};
void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> client,
@ -61,7 +64,18 @@ s32 MmLeaveRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_
s32 MmGetWorldInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetWorldInfoListRequest& request);
s32 MmSearchRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SearchRoomRequest& request);
const OrbisNpMatching2SearchRoomRequest& request, bool a_variant = false);
s32 MmGetRoomDataExternalList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetRoomDataExternalListRequest& request,
bool a_variant = false);
s32 MmGetRoomMemberDataExternalList(
OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetRoomMemberDataExternalListRequest& request, bool a_variant = false);
s32 MmGetUserInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetUserInfoListRequest& request,
bool a_variant = false);
s32 MmSetUserInfo(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetUserInfoRequest& request);
s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataInternalRequest& request);
s32 MmSetRoomDataExternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,

View File

@ -147,6 +147,8 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const bool transport_ready = Stubs::EnsureTransport();
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
@ -177,7 +179,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
fmt::ptr(callbackArg), ctx.compiled_sdk_version, ctx.bound_port);
}
if (Stubs::TransportIsReady()) {
if (transport_ready) {
SendStunPing(ctx_id);
}
return ORBIS_OK;
@ -203,6 +205,8 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void*
// Account id isn't needed without matchmaking wired up; leave it 0 for now.
const OrbisNpAccountId account_id = 0;
const bool transport_ready = Stubs::EnsureTransport();
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
@ -234,7 +238,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void*
OnlineIdToString(ctx.owner_online_id), account_id);
}
if (Stubs::TransportIsReady()) {
if (transport_ready) {
SendStunPing(ctx_id);
}
return ORBIS_OK;
@ -297,6 +301,8 @@ s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxI
s32 cid = 0;
bool reused_established = false;
bool queue_activation = false;
bool start_handshake = true;
{
SignalingMutexGuard lock;
if (!g_initialized) {
@ -322,12 +328,18 @@ s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxI
} else if (conn_it != g_connections.end()) {
cid = existing_it->second;
ConnectionInfo& ci = conn_it->second;
const bool was_locally_activated = ci.locally_activated;
ci.locally_activated = true;
if (ci.state == ConnState::Established) {
ClearLingerAndTimeoutLocked(cid);
reused_established = true;
start_handshake = false;
} else {
ClearLingerAndTimeoutLocked(cid);
start_handshake = true;
}
if (!was_locally_activated) {
queue_activation = true;
}
}
}
@ -352,23 +364,30 @@ s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxI
}
if (created_new) {
ArmConnectTimeoutLocked(cid);
QueueActivationLocked(cid, peer_online_id_str);
queue_activation = true;
start_handshake = true;
}
if (queue_activation) {
QueueActivationLocked(cid, peer_online_id_str, start_handshake);
}
LOG_INFO(Lib_NpSignaling, "ctxId={} peer='{}' connId={} Status: {}", ctxId,
peer_online_id_str, cid,
created_new ? "queued, async"
: reused_established ? "reused established"
: reused_established ? "queued local activation on established connection"
: queue_activation ? "queued local activation on existing connection"
: "reused transient");
}
*outConnId = cid;
if (reused_established) {
EstablishConnection(cid, false);
if (queue_activation) {
g_dispatch_cv.notify_all();
return ORBIS_OK;
}
g_dispatch_cv.notify_all();
if (reused_established) {
EstablishConnection(cid, false);
}
return ORBIS_OK;
}

View File

@ -6,6 +6,7 @@
#include <string>
#include "common/types.h"
#include "core/libraries/np/np_types2.h"
namespace Core::Loader {
class SymbolsResolver;
@ -13,66 +14,6 @@ class SymbolsResolver;
namespace Libraries::Np::NpSignaling {
using OrbisNpSignalingContextId = s32;
using OrbisNpSignalingConnectionId = s32;
using OrbisNpSignalingRequestId = u32;
using OrbisNpSignalingHandler = PS4_SYSV_ABI void (*)(u32 ctxId, u32 connId, s32 event,
s32 errorCode, void* userArg);
constexpr s32 ORBIS_NP_SIGNALING_EVENT_DEAD = 0;
constexpr s32 ORBIS_NP_SIGNALING_EVENT_ESTABLISHED = 1;
constexpr s32 ORBIS_NP_SIGNALING_EVENT_NETINFO_ERROR = 2;
constexpr s32 ORBIS_NP_SIGNALING_EVENT_NETINFO_RESULT = 3;
constexpr s32 ORBIS_NP_SIGNALING_EVENT_PEER_ACTIVATED = 10;
constexpr s32 ORBIS_NP_SIGNALING_EVENT_PEER_DEACTIVATED = 11;
constexpr s32 ORBIS_NP_SIGNALING_EVENT_MUTUAL_ACTIVATED = 12;
constexpr s32 ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE = 0;
constexpr s32 ORBIS_NP_SIGNALING_CONN_STATUS_PENDING = 1;
constexpr s32 ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE = 2;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_RTT = 1;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_BANDWIDTH = 2;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_PEER_NP_ID = 3;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_PEER_ADDR = 4;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_MAPPED_ADDR = 5;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_PACKET_LOSS = 6;
constexpr s32 ORBIS_NP_SIGNALING_CONN_INFO_PEER_ADDRESS_A = 7;
constexpr s32 ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG = 1;
struct OrbisNpSignalingNetInfo {
u64 size;
u32 localAddr;
u32 mappedAddr;
s32 natStatus;
u32 _pad_14;
};
static_assert(sizeof(OrbisNpSignalingNetInfo) == 0x18);
struct OrbisNpSignalingAccountPlatformPair {
u64 accountId;
u32 platformType;
u32 _pad_0c;
};
static_assert(sizeof(OrbisNpSignalingAccountPlatformPair) == 0x10);
struct OrbisNpSignalingMemoryInfo {
u64 currentInUse;
u64 peakInUse;
u64 maxSystemSize;
};
static_assert(sizeof(OrbisNpSignalingMemoryInfo) == 0x18);
struct OrbisNpSignalingConnectionStatistics {
u32 peakConnectionCount;
u32 activeConnectionCount;
u32 transientConnectionCount;
u32 establishedConnectionCount;
};
static_assert(sizeof(OrbisNpSignalingConnectionStatistics) == 0x10);
s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s32 cpuAffinityMask,
s64 threadStackSize);
s32 PS4_SYSV_ABI sceNpSignalingTerminate();

View File

@ -370,7 +370,7 @@ static void StopReceiveThread() {
static void PingThreadMain() {
while (!g_ping_stop) {
if (!Stubs::TransportIsReady()) {
if (!Stubs::EnsureTransport()) {
std::this_thread::sleep_for(std::chrono::milliseconds(kSigRetryMs));
continue;
}
@ -400,6 +400,7 @@ static void PingThreadMain() {
StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, cs.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = Stubs::AdvertisedAddr();
Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_port);
}

View File

@ -897,8 +897,9 @@ void StartHandshakeInitiator(OrbisNpSignalingConnectionId conn_id) {
}
}
void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id) {
g_pending_activations.push_back({conn_id, std::string(peer_online_id)});
void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id,
bool start_handshake) {
g_pending_activations.push_back({conn_id, std::string(peer_online_id), start_handshake});
}
void ProcessPendingActivations() {
@ -912,6 +913,7 @@ void ProcessPendingActivations() {
}
for (const PendingActivation& act : work) {
bool already_established = false;
{
SignalingMutexGuard lock;
const auto it = g_connections.find(act.conn_id);
@ -940,8 +942,16 @@ void ProcessPendingActivations() {
it->second.addr = peer_addr;
it->second.port = peer_port;
SendActivationRequestLocked(it->second);
already_established = it->second.state == ConnState::Established;
LOG_INFO(Lib_NpSignaling, "connection {} sent local activation to '{}' at {:#x}:{}{}",
act.conn_id, act.peer_online_id, peer_addr, sceNetNtohs(peer_port),
act.start_handshake ? "" : " (existing connection)");
}
if (act.start_handshake) {
StartHandshakeInitiator(act.conn_id);
} else if (already_established) {
EstablishConnection(act.conn_id, false);
}
StartHandshakeInitiator(act.conn_id);
}
}
@ -1154,11 +1164,16 @@ void SendActivationRequestLocked(const ConnectionInfo& ci) {
void SendControlCloseLocked(const ConnectionInfo& ci, ControlReason reason) {
if (ci.addr == 0 || ci.port == 0) {
LOG_WARNING(Lib_NpSignaling,
"Control Close SEND skipped: connId={} reason={} unresolved endpoint",
ci.conn_id, static_cast<u16>(reason));
return;
}
SignalingControl pkt = MakeControlLocked(ci, ControlKind::Close);
pkt.reason = static_cast<u16>(reason);
Stubs::ControlSendTo(&pkt, sizeof(pkt), ci.addr, ci.port);
const int rc = Stubs::ControlSendTo(&pkt, sizeof(pkt), ci.addr, ci.port);
LOG_INFO(Lib_NpSignaling, "Control Close SEND: connId={} reason={} dst={:#x}:{} rc={}",
ci.conn_id, static_cast<u16>(reason), ci.addr, sceNetNtohs(ci.port), rc);
}
void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& pkt) {
@ -1236,6 +1251,9 @@ void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& p
ack_addr = ci.addr;
ack_port = ci.port;
} else if (conn_id == 0) {
LOG_INFO(Lib_NpSignaling,
"Control RECV ignored: kind={} from='{}' src={:#x}:{} no matching connection",
pkt.kind, from_id, from_addr, sceNetNtohs(from_port));
return;
} else if (kind == ControlKind::ActivationAck) {
ConnectionInfo& ci = g_connections[conn_id];
@ -1252,6 +1270,10 @@ void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& p
} else if (kind == ControlKind::Close) {
ConnectionInfo& ci = g_connections[conn_id];
const ControlReason reason = static_cast<ControlReason>(pkt.reason);
LOG_INFO(Lib_NpSignaling,
"Control Close RECV: connId={} from='{}' reason={} src={:#x}:{} state={}",
conn_id, from_id, pkt.reason, from_addr, sceNetNtohs(from_port),
static_cast<s32>(ci.state));
if (reason == ControlReason::Deactivate && ci.state == ConnState::Established) {
peer_deactivated_conn = conn_id;
} else {
@ -1290,7 +1312,7 @@ void SendStunPing(s32 ctx_id) {
online_id = it->second.owner_online_id;
}
if (!Stubs::TransportIsReady()) {
if (!Stubs::EnsureTransport()) {
return;
}
@ -1306,9 +1328,10 @@ void SendStunPing(s32 ctx_id) {
StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = Stubs::AdvertisedAddr();
LOG_DEBUG(Lib_NpSignaling, "ctxId={} online_id='{}' server={:#x}:{}", ctx_id,
OnlineIdToString(online_id), server_addr, sceNetNtohs(server_udp));
LOG_DEBUG(Lib_NpSignaling, "ctxId={} online_id='{}' server={:#x}:{} local_ip={:#x}", ctx_id,
OnlineIdToString(online_id), server_addr, sceNetNtohs(server_udp), ping.local_ip);
Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp);
}

View File

@ -253,6 +253,7 @@ extern std::unordered_map<s32, PeerNetInfoResult> g_peer_netinfo_results;
struct PendingActivation {
OrbisNpSignalingConnectionId conn_id = 0;
std::string peer_online_id;
bool start_handshake = true;
};
extern std::vector<PendingActivation> g_pending_activations;
extern u32 g_peer_netinfo_next_id;
@ -331,7 +332,8 @@ void TerminateConnectionFaithful(OrbisNpSignalingConnectionId conn_id);
void StartHandshakeInitiator(OrbisNpSignalingConnectionId conn_id);
void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id);
void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id,
bool start_handshake = true);
void ProcessPendingActivations();
void HandleHandshakePacket(u32 from_addr, u16 from_port, const SignalingHandshake& pkt);

View File

@ -162,6 +162,13 @@ s32 PS4_SYSV_ABI sceNpTusGetDataAsync(int reqId, OrbisNpId* npId, OrbisNpTusSlot
Lib_NpTus,
"reqId = {:#x}, slotId = {}, dataStatusSize = {}, data = {}, dataSize = {}, option = {}",
reqId, slotId, dataStatusSize, data, dataSize, fmt::ptr(option));
NpTusRequest* req = nullptr;
if (auto ret = GetRequest(reqId, &req); ret < 0) {
return ret;
}
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -248,10 +255,7 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync(int reqId, OrbisNpId* npId,
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -304,10 +308,7 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync(int reqId, OrbisNpId* npIds
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -358,10 +359,7 @@ s32 PS4_SYSV_ABI sceNpTusSetDataAsync(int reqId, OrbisNpId* npId, OrbisNpTusSlot
if (auto ret = GetRequest(reqId, &req); ret < 0) {
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -431,10 +429,7 @@ s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync(int reqId, OrbisNpId* npId,
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -684,10 +679,7 @@ s32 PS4_SYSV_ABI sceNpTusGetDataAAsync(int reqId, OrbisNpAccountId accountId,
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -814,10 +806,7 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync(int reqId, OrbisNpAccountI
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}
@ -1029,10 +1018,7 @@ s32 PS4_SYSV_ABI sceNpTusSetDataAAsync(int reqId, OrbisNpAccountId accountId,
return ret;
}
req->Start([=]() {
//
return 0;
});
req->Start([=]() { return 0; });
return ORBIS_OK;
}

View File

@ -159,19 +159,19 @@ s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback(s32 titleUserCtxI
}
s32 PS4_SYSV_ABI sceNpWebApiAbortHandle(s32 libCtxId, s32 handleId) {
LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId);
LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId);
return abortHandle(libCtxId, handleId);
}
s32 PS4_SYSV_ABI sceNpWebApiAbortRequest(s64 requestId) {
LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}", requestId);
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}", requestId);
return abortRequest(requestId);
}
s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader(s64 requestId, const char* pFieldName,
const char* pValue) {
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}, pFieldName = '{}', pValue = '{}'",
requestId, (pFieldName ? pFieldName : "null"), (pValue ? pValue : "null"));
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pFieldName = '{}', pValue = '{}'", requestId,
(pFieldName ? pFieldName : "null"), (pValue ? pValue : "null"));
if (pFieldName == nullptr || pValue == nullptr)
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
return addHttpRequestHeaderInternal(requestId, pFieldName, pValue);
@ -180,15 +180,9 @@ s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader(s64 requestId, const char* pFie
s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart(s64 requestId,
const OrbisNpWebApiMultipartPartParameter* pParam,
s32* pIndex) {
LOG_INFO(Lib_NpWebApi,
"called (STUBBED) : requestId = {:#x}, "
"pParam = {}, pIndex = {}",
requestId, fmt::ptr(pParam), fmt::ptr(pIndex));
if (pParam) {
LOG_ERROR(Lib_NpWebApi, " Part params: headerNum = {}, contentLength = {}",
pParam->headerNum, pParam->contentLength);
}
return ORBIS_OK;
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, headerNum = {}, contentLength = {}",
requestId, (pParam ? pParam->headerNum : 0), (pParam ? pParam->contentLength : 0));
return addMultipartPart(requestId, pParam, pIndex);
}
void PS4_SYSV_ABI sceNpWebApiCheckTimeout() {
@ -202,7 +196,7 @@ void PS4_SYSV_ABI sceNpWebApiCheckTimeout() {
s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection(s32 userCtxId,
bool bRemainKeepAliveConnection) {
LOG_ERROR(Lib_NpWebApi,
"called (STUBBED) : userCtxId = {:#x}, "
"called (STUBBED), userCtxId = {:#x}, "
"bRemainKeepAliveConnection = {}",
userCtxId, bRemainKeepAliveConnection);
return ORBIS_OK;
@ -211,7 +205,7 @@ s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection(s32 userCtxId,
s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection(s32 userCtxId, const char* pApiGroup,
bool bRemainKeepAliveConnection) {
LOG_ERROR(Lib_NpWebApi,
"called (STUBBED) : userCtxId = {:#x}, "
"called (STUBBED), userCtxId = {:#x}, "
"pApiGroup = '{}', bRemainKeepAliveConnection = {}",
userCtxId, (pApiGroup ? pApiGroup : "null"), bRemainKeepAliveConnection);
return ORBIS_OK;
@ -262,7 +256,7 @@ s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest(s32 titleUserCtxId, const cha
}
LOG_INFO(Lib_NpWebApi,
"called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
"called, titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method));
return createRequest(titleUserCtxId, pApiGroup, pPath, method, nullptr, nullptr, pRequestId,
@ -288,7 +282,7 @@ s32 PS4_SYSV_ABI sceNpWebApiCreateRequest(s32 titleUserCtxId, const char* pApiGr
}
LOG_INFO(Lib_NpWebApi,
"called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
"called, titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method));
return createRequest(titleUserCtxId, pApiGroup, pPath, method, pContentParameter, nullptr,
@ -318,7 +312,7 @@ s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest(s64 requestId) {
s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats(s32 userCtxId, const char* pApiGroup,
OrbisNpWebApiConnectionStats* pStats) {
LOG_ERROR(Lib_NpWebApi,
"called (STUBBED) : userCtxId = {:#x}, "
"called (STUBBED), userCtxId = {:#x}, "
"pApiGroup = '{}', pStats = {}",
userCtxId, (pApiGroup ? pApiGroup : "null"), fmt::ptr(pStats));
return ORBIS_OK;
@ -326,13 +320,13 @@ s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats(s32 userCtxId, const char* pApiGr
s32 PS4_SYSV_ABI sceNpWebApiGetErrorCode() {
const s32 code = getLastWebApiError();
LOG_INFO(Lib_NpWebApi, "called : lastErrorCode = {:#x}", code);
LOG_INFO(Lib_NpWebApi, "called, lastErrorCode = {:#x}", code);
return code;
}
s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue(s64 requestId, const char* pFieldName,
char* pValue, u64 valueSize) {
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}, pFieldName = '{}', valueSize = {}",
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pFieldName = '{}', valueSize = {}",
requestId, (pFieldName ? pFieldName : "null"), valueSize);
if (pFieldName == nullptr || pValue == nullptr || valueSize == 0)
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
@ -341,7 +335,7 @@ s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue(s64 requestId, const char
s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength(s64 requestId, const char* pFieldName,
u64* pValueLength) {
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}, pFieldName = '{}'", requestId,
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pFieldName = '{}'", requestId,
(pFieldName ? pFieldName : "null"));
if (pFieldName == nullptr || pValueLength == nullptr)
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
@ -349,7 +343,7 @@ s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength(s64 requestId, cons
}
s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode(s64 requestId, s32* out_status_code) {
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}", requestId);
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}", requestId);
// On newer SDKs, NULL output pointer is invalid
if (getCompiledSdkVersion() > Common::ElfInfo::FW_100 && out_status_code == nullptr)
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
@ -359,13 +353,13 @@ s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode(s64 requestId, s32* out_status_cod
s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats(s32 libCtxId,
OrbisNpWebApiMemoryPoolStats* pCurrentStat) {
LOG_ERROR(Lib_NpWebApi, "called (STUBBED) : libCtxId = {:#x}, pCurrentStat = {}", libCtxId,
LOG_ERROR(Lib_NpWebApi, "called (STUBBED), libCtxId = {:#x}, pCurrentStat = {}", libCtxId,
fmt::ptr(pCurrentStat));
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApiInitialize(s32 libHttpCtxId, u64 poolSize) {
LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
LOG_INFO(Lib_NpWebApi, "called, libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
poolSize);
if (!g_is_initialized) {
g_is_initialized = true;
@ -383,7 +377,7 @@ s32 PS4_SYSV_ABI sceNpWebApiInitialize(s32 libHttpCtxId, u64 poolSize) {
}
s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence(s32 libHttpCtxId, u64 poolSize) {
LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
LOG_INFO(Lib_NpWebApi, "called, libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
poolSize);
if (!g_is_initialized) {
g_is_initialized = true;
@ -429,7 +423,7 @@ s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest(
}
LOG_INFO(Lib_NpWebApi,
"called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
"called, titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method));
return createRequest(titleUserCtxId, pApiGroup, pPath, method, pContentParameter, pInternalArgs,
@ -496,7 +490,7 @@ s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA(
}
s32 PS4_SYSV_ABI sceNpWebApiReadData(s64 requestId, void* pData, u64 size) {
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}, pData = {}, size = {:#x}", requestId,
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pData = {}, size = {:#x}", requestId,
fmt::ptr(pData), size);
if (pData == nullptr || size == 0)
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
@ -563,27 +557,25 @@ s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout(s32 libCtxId, s32 handleId, u32 tim
}
s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection(s32 libCtxId, s32 maxConnection) {
LOG_ERROR(Lib_NpWebApi, "called (STUBBED) : libCtxId = {:#x}, maxConnection = {}", libCtxId,
LOG_ERROR(Lib_NpWebApi, "called (STUBBED), libCtxId = {:#x}, maxConnection = {}", libCtxId,
maxConnection);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType(s64 requestId, const char* pTypeName,
const char* pBoundary) {
LOG_ERROR(Lib_NpWebApi,
"called (STUBBED) : requestId = {:#x}, "
"pTypeName = '{}', pBoundary = '{}'",
requestId, (pTypeName ? pTypeName : "null"), (pBoundary ? pBoundary : "null"));
return ORBIS_OK;
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pTypeName = '{}', pBoundary = '{}'",
requestId, (pTypeName ? pTypeName : "null"), (pBoundary ? pBoundary : "null"));
return setMultipartContentType(requestId, pTypeName, pBoundary);
}
s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout(s64 requestId, u32 timeout) {
LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}, timeout = {} ms", requestId, timeout);
LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, timeout = {} ms", requestId, timeout);
return setRequestTimeout(requestId, timeout);
}
s32 PS4_SYSV_ABI sceNpWebApiTerminate(s32 libCtxId) {
LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}", libCtxId);
LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}", libCtxId);
s32 result = terminateContext(libCtxId);
if (result != ORBIS_OK) {
return result;
@ -637,7 +629,7 @@ s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId(const char* pJsonNpId,
}
s32 PS4_SYSV_ABI sceNpWebApiVshInitialize(s32 libHttpCtxId, u64 poolSize) {
LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
LOG_INFO(Lib_NpWebApi, "called, libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
poolSize);
if (!g_is_initialized) {
g_is_initialized = true;

View File

@ -146,5 +146,18 @@ struct PushEventInput {
};
void EnqueuePushEvent(const PushEventInput& ev);
s32 PS4_SYSV_ABI sceNpWebApiInitialize(s32 libHttpCtxId, u64 poolSize);
s32 PS4_SYSV_ABI sceNpWebApiTerminate(s32 libCtxId);
s32 PS4_SYSV_ABI sceNpWebApiCreateContextA(s32 libCtxId,
Libraries::UserService::OrbisUserServiceUserId userId);
s32 PS4_SYSV_ABI sceNpWebApiDeleteContext(s32 titleUserCtxId);
s32 PS4_SYSV_ABI sceNpWebApiCreateRequest(s32 titleUserCtxId, const char* pApiGroup,
const char* pPath, OrbisNpWebApiHttpMethod method,
const OrbisNpWebApiContentParameter* pContentParameter,
s64* pRequestId);
s32 PS4_SYSV_ABI sceNpWebApiSendRequest2(s64 requestId, const void* pData, u64 dataSize,
OrbisNpWebApiResponseInformationOption* pRespInfoOption);
s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest(s64 requestId);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi

View File

@ -579,6 +579,94 @@ void checkRequestTimeout(OrbisNpWebApiRequest* request) {
}
}
s32 setMultipartContentType(s64 requestId, const char* pTypeName, const char* pBoundary) {
OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
if (context == nullptr) {
return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
if (user_context == nullptr) {
releaseContext(context);
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
if (request == nullptr) {
releaseUserContext(user_context);
releaseContext(context);
return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
}
request->multipartContentType = (pTypeName != nullptr) ? pTypeName : "";
request->multipartBoundary = (pBoundary != nullptr) ? pBoundary : "";
releaseRequest(request);
releaseUserContext(user_context);
releaseContext(context);
return ORBIS_OK;
}
s32 addMultipartPart(s64 requestId, const OrbisNpWebApiMultipartPartParameter* pParam,
s32* pIndex) {
if (pParam == nullptr) {
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
}
OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
if (context == nullptr) {
return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
if (user_context == nullptr) {
releaseContext(context);
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
if (request == nullptr) {
releaseUserContext(user_context);
releaseContext(context);
return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
}
// The library supplies Content-Length itself (from contentLength), so skip an app-provided one.
const auto isContentLength = [](const char* name) {
const char* t = "content-length";
for (; *name != '\0' && *t != '\0'; ++name, ++t) {
char c = *name;
if (c >= 'A' && c <= 'Z') {
c = static_cast<char>(c - 'A' + 'a');
}
if (c != *t) {
return false;
}
}
return *name == '\0' && *t == '\0';
};
OrbisNpWebApiRequest::MultipartPart part;
part.contentLength = pParam->contentLength;
std::string headers;
for (u64 i = 0; i < pParam->headerNum; ++i) {
const OrbisNpWebApiHttpHeader& h = pParam->pHeaders[i];
if (h.pName == nullptr || h.pValue == nullptr || isContentLength(h.pName)) {
continue;
}
headers += h.pName;
headers += ": ";
headers += h.pValue;
headers += "\r\n";
}
headers += "Content-Length: ";
headers += std::to_string(pParam->contentLength);
headers += "\r\n\r\n";
part.rawHeaders = std::move(headers);
request->multipartParts.push_back(std::move(part));
if (pIndex != nullptr) {
*pIndex = static_cast<s32>(request->multipartParts.size()); // 1-based part index
}
releaseRequest(request);
releaseUserContext(user_context);
releaseContext(context);
return ORBIS_OK;
}
s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s8 flag,
OrbisNpWebApiResponseInformationOption* pRespInfoOption) {
OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
@ -601,7 +689,69 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
startRequestTimer(request);
// TODO: multipart logic
// Multipart requests accumulate each part payload across sendMultipartRequest() calls,the
// full multipart/mixed body is framed and transmitted once every declared part is complete.
std::string multipartBody;
const void* sendData = pData;
u64 sendSize = dataSize;
if (request->multipart) {
if (partIndex < 1 || partIndex > static_cast<s32>(request->multipartParts.size())) {
releaseRequest(request);
releaseUserContext(user_context);
releaseContext(context);
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
}
auto& part = request->multipartParts[partIndex - 1];
if (pData != nullptr && dataSize > 0) {
part.data.append(static_cast<const char*>(pData), dataSize);
}
part.sentSize += dataSize;
bool allComplete = true;
for (const auto& p : request->multipartParts) {
if (p.sentSize < p.contentLength) {
allComplete = false;
break;
}
}
if (!allComplete) {
// Further part payloads still expected; nothing to transmit yet.
releaseRequest(request);
releaseUserContext(user_context);
releaseContext(context);
return ORBIS_OK;
}
std::string boundary = request->multipartBoundary;
if (boundary.empty()) {
// Fallback boundary derived from the unique request id.
static const char kHex[] = "0123456789abcdef";
boundary = "----shadPS4Boundary";
const u64 v = static_cast<u64>(requestId);
for (int shift = 60; shift >= 0; shift -= 4) {
boundary += kHex[(v >> shift) & 0xf];
}
}
const std::string typeName = request->multipartContentType.empty()
? std::string("multipart/mixed")
: request->multipartContentType;
for (const auto& p : request->multipartParts) {
multipartBody += "--";
multipartBody += boundary;
multipartBody += "\r\n";
multipartBody += p.rawHeaders; // header lines + blank line separating headers/body
multipartBody += p.data;
multipartBody += "\r\n";
}
multipartBody += "--";
multipartBody += boundary;
multipartBody += "--\r\n";
request->userContentType = typeName + "; boundary=" + boundary;
request->userContentLength = multipartBody.size();
sendData = multipartBody.data();
sendSize = multipartBody.size();
}
if (g_sdk_ver >= Common::ElfInfo::FW_250 && !request->sent) {
request->sent = true;
@ -634,7 +784,7 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
const s32 tmpl_id = Libraries::Http::sceHttpCreateTemplate(
context->libHttpCtxId, "libhttp", /*httpVer=*/2, /*isAutoProxyConf=*/0);
if (tmpl_id < 0) {
LOG_ERROR(Lib_NpWebApi, "sendRequest: sceHttpCreateTemplate failed: {:#x}", tmpl_id);
LOG_ERROR(Lib_NpWebApi, "sceHttpCreateTemplate failed: {:#x}", tmpl_id);
releaseRequest(request);
releaseUserContext(user_context);
releaseContext(context);
@ -644,8 +794,7 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
const int conn_id = Libraries::Http::sceHttpCreateConnectionWithURL(
tmpl_id, base_url.c_str(), /*enableKeepalive=*/true);
if (conn_id < 0) {
LOG_ERROR(Lib_NpWebApi, "sendRequest: sceHttpCreateConnectionWithURL failed: {:#x}",
conn_id);
LOG_ERROR(Lib_NpWebApi, "sceHttpCreateConnectionWithURL failed: {:#x}", conn_id);
Libraries::Http::sceHttpDeleteTemplate(tmpl_id);
request->http_template_id = 0;
releaseRequest(request);
@ -679,7 +828,7 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
sceMethod = 8; // out-of-band PATCH marker recognised by libhttp
break;
default:
LOG_ERROR(Lib_NpWebApi, "sendRequest: unknown method enum value {}",
LOG_ERROR(Lib_NpWebApi, "unknown method enum value {}",
static_cast<int>(request->userMethod));
releaseRequest(request);
releaseUserContext(user_context);
@ -690,8 +839,7 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
const int req_id = Libraries::Http::sceHttpCreateRequestWithURL(
conn_id, sceMethod, full_url.c_str(), request->userContentLength);
if (req_id < 0) {
LOG_ERROR(Lib_NpWebApi, "sendRequest: sceHttpCreateRequestWithURL failed: {:#x}",
req_id);
LOG_ERROR(Lib_NpWebApi, "sceHttpCreateRequestWithURL failed: {:#x}", req_id);
Libraries::Http::sceHttpDeleteConnection(conn_id);
request->http_connection_id = 0;
Libraries::Http::sceHttpDeleteTemplate(tmpl_id);
@ -718,13 +866,35 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
/*mode=*/0);
} else {
LOG_WARNING(Lib_NpWebApi,
"sendRequest: no bearer token for user_id={}; request to '{}' will "
"no bearer token for user_id={}; request to '{}' will "
"be unauthenticated (expect 401 from server)",
user_context->userId, request->userPath);
}
// Replay app-supplied headers
// Replay app-supplied headers. Content-Type is already emitted above from the
// ContentParameter (userContentType),skip a duplicate app-supplied Content-Type so the
// request never carries two
const auto isContentType = [](const std::string& name) {
constexpr std::string_view target = "content-type";
if (name.size() != target.size()) {
return false;
}
for (size_t i = 0; i < name.size(); ++i) {
char c = name[i];
if (c >= 'A' && c <= 'Z') {
c = static_cast<char>(c - 'A' + 'a');
}
if (c != target[i]) {
return false;
}
}
return true;
};
const bool haveContentType = !request->userContentType.empty();
for (const auto& [hname, hvalue] : request->userHeaders) {
if (haveContentType && isContentType(hname)) {
continue;
}
Libraries::Http::sceHttpAddRequestHeader(req_id, hname.c_str(), hvalue.c_str(),
/*mode=*/0);
}
@ -733,17 +903,16 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
setRequestState(request, 4);
const s32 send_err =
Libraries::Http::sceHttpSendRequest(request->http_request_id, pData, dataSize);
Libraries::Http::sceHttpSendRequest(request->http_request_id, sendData, sendSize);
if (send_err < 0) {
LOG_ERROR(Lib_NpWebApi, "sendRequest: sceHttpSendRequest failed: {:#x}", send_err);
LOG_ERROR(Lib_NpWebApi, "sceHttpSendRequest failed: {:#x}", send_err);
releaseRequest(request);
releaseUserContext(user_context);
releaseContext(context);
return send_err;
}
LOG_INFO(Lib_NpWebApi,
"sendRequest OK requestId={:#x} apiGroup='{}' path='{}' method={} httpReqId={}",
LOG_INFO(Lib_NpWebApi, "requestId={:#x} apiGroup='{}' path='{}' method={} httpReqId={}",
requestId, request->userApiGroup, request->userPath,
magic_enum::enum_name(request->userMethod), request->http_request_id);
@ -1082,8 +1251,7 @@ s32 createPushEventFilterInternal(OrbisNpWebApiContext* context,
filter->parentContext = context;
filter->filterId = filterId;
LOG_INFO(Lib_NpWebApi, "createPushEventFilter: filterId={} dataTypeParams={}", filterId,
filterParamNum);
LOG_INFO(Lib_NpWebApi, "filterId={} dataTypeParams={}", filterId, filterParamNum);
if (pFilterParam != nullptr && filterParamNum != 0) {
for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) {
OrbisNpWebApiPushEventFilterParameter copy = OrbisNpWebApiPushEventFilterParameter{};
@ -1212,7 +1380,7 @@ s32 createServicePushEventFilterInternal(
if (pNpServiceName != nullptr && !EmulatorSettings.IsShadNetEnabled()) {
// Seems sceNpManagerIntGetUserList fails?
LOG_DEBUG(Lib_NpWebApi, "Cannot create service push event while PSN is disabled");
LOG_DEBUG(Lib_NpWebApi, "Cannot create service push event while shadNet is disabled");
handle->userCount--;
return ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND;
}
@ -1231,19 +1399,19 @@ s32 createServicePushEventFilterInternal(
if (pNpServiceName == nullptr) {
filter->internal = true;
} else {
// TODO: if pNpServiceName is non-null, create an np request for this filter.
LOG_ERROR(Lib_NpWebApi, "Np behavior not handled");
filter->npServiceName = std::string(pNpServiceName);
}
filter->npServiceLabel = npServiceLabel;
LOG_INFO(Lib_NpWebApi, "filterId={} dataTypeParams={}", filterId, filterParamNum);
if (pFilterParam != nullptr && filterParamNum != 0) {
for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) {
OrbisNpWebApiServicePushEventFilterParameter copy =
OrbisNpWebApiServicePushEventFilterParameter{};
memcpy(&copy, &pFilterParam[param_idx],
sizeof(OrbisNpWebApiServicePushEventFilterParameter));
LOG_INFO(Lib_NpWebApi, " filterParam[{}] data_type='{}'", param_idx,
copy.dataType.val);
filter->filterParams.emplace_back(copy);
}
}
@ -1389,7 +1557,7 @@ s32 createExtendedPushEventFilterInternal(
if (pNpServiceName != nullptr && !EmulatorSettings.IsShadNetEnabled()) {
// Seems sceNpManagerIntGetUserList fails?
LOG_DEBUG(Lib_NpWebApi, "Cannot create extended push event while PSN is disabled");
LOG_DEBUG(Lib_NpWebApi, "Cannot create extended push event while shadNet is disabled");
handle->userCount--;
return ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND;
}
@ -1409,16 +1577,13 @@ s32 createExtendedPushEventFilterInternal(
if (pNpServiceName == nullptr) {
npServiceLabel = ORBIS_NP_INVALID_SERVICE_LABEL;
} else {
// TODO: if pNpServiceName is non-null, create an np request for this filter.
LOG_ERROR(Lib_NpWebApi, "Np behavior not handled");
filter->npServiceName = std::string(pNpServiceName);
}
filter->npServiceLabel = npServiceLabel;
LOG_INFO(Lib_NpWebApi,
"createExtdPushEventFilter: filterId={} service='{}' label={:#x} dataTypeParams={}",
filterId, pNpServiceName ? pNpServiceName : "null", npServiceLabel, filterParamNum);
LOG_INFO(Lib_NpWebApi, "filterId={} service='{}' label={:#x} dataTypeParams={}", filterId,
pNpServiceName ? pNpServiceName : "null", npServiceLabel, filterParamNum);
if (pFilterParam != nullptr && filterParamNum != 0) {
for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) {
OrbisNpWebApiExtdPushEventFilterParameter copy =
@ -1665,7 +1830,7 @@ s32 addHttpRequestHeaderInternal(s64 requestId, const char* pFieldName, const ch
releaseContext(context);
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId);
if (request == nullptr) {
releaseUserContext(user_context);
releaseContext(context);
@ -1736,7 +1901,7 @@ s32 PS4_SYSV_ABI getHttpStatusCodeInternal(s64 requestId, s32* out_status_code)
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId);
if (request == nullptr) {
releaseUserContext(user_context);
releaseContext(context);
@ -1819,7 +1984,7 @@ s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size) {
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
}
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId);
if (request == nullptr) {
releaseUserContext(user_context);
releaseContext(context);
@ -1877,7 +2042,7 @@ s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size) {
}
if (result > 0) {
LOG_INFO(Lib_NpWebApi, "readData reqId={:#x} -> {} bytes: {:.256s}", requestId, result,
LOG_INFO(Lib_NpWebApi, "reqId={:#x} -> {} bytes: {:.256s}", requestId, result,
std::string(reinterpret_cast<const char*>(pData),
std::min<u64>(result, 256))); // debug to be removed
}
@ -1899,13 +2064,16 @@ s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size) {
// service dispatch, with matching done by FUN_010049c0. Natively this is driven by the
// NP manager's push listener thread (mnp:usr:npweblis)
using ServiceCb = PS4_SYSV_ABI void (*)(s32, s32, const char*, OrbisNpServiceLabel,
const OrbisNpWebApiPushEventDataType*, const char*, u64,
void*);
struct PushPeerAddress {
OrbisNpOnlineId onlineId;
s32 platform;
};
// Service callback = basic callback with pNpServiceName/npServiceLabel inserted after
// callbackId; it still carries pTo/pFrom before pDataType.
using ServiceCb = PS4_SYSV_ABI void (*)(s32, s32, const char*, OrbisNpServiceLabel,
const PushPeerAddress*, const PushPeerAddress*,
const OrbisNpWebApiPushEventDataType*, const char*, u64,
void*);
using BasicCb = PS4_SYSV_ABI void (*)(s32, s32, const PushPeerAddress*, const PushPeerAddress*,
const OrbisNpWebApiPushEventDataType*, const char*, u64,
void*);
@ -2014,8 +2182,7 @@ void DrainPushEvents() {
exarr.empty() ? nullptr : exarr.data();
LOG_INFO(
Lib_NpWebApi,
"DrainPushEvents: invoking extd cb ctx={:#x} cbId={} dataType='{}'",
Lib_NpWebApi, "invoking extd cb ctx={:#x} cbId={} dataType='{}'",
title_user_ctx_id, cbId,
ev.dataType); // debug confirm the listener callback fires. to be removed
reinterpret_cast<ExtdCbA>(raw)(
@ -2039,9 +2206,17 @@ void DrainPushEvents() {
const char* svc =
flt->npServiceName.empty() ? nullptr : flt->npServiceName.c_str();
const char* svc_data = ev.data.empty() ? nullptr : ev.data.data();
PushPeerAddress to_peer{}; // notified user (self)
PushPeerAddress from_peer{}; // user that caused the event
if (ev.hasTo) {
to_peer.onlineId = ev.toOnlineId;
}
if (ev.hasFrom) {
from_peer.onlineId = ev.fromOnlineId;
}
reinterpret_cast<ServiceCb>(reinterpret_cast<void (*)()>(cb->cbFunc))(
title_user_ctx_id, cbId, svc, flt->npServiceLabel, &dt, svc_data,
ev.data.size(), cb->pUserArg);
title_user_ctx_id, cbId, svc, flt->npServiceLabel, &to_peer, &from_peer,
&dt, svc_data, ev.data.size(), cb->pUserArg);
}
// Basic push
@ -2066,7 +2241,7 @@ void DrainPushEvents() {
continue;
}
LOG_INFO(Lib_NpWebApi,
"DrainPushEvents: invoking basic cb ctx={:#x} cbId={} "
"invoking basic cb ctx={:#x} cbId={} "
"dataType='{}'",
title_user_ctx_id, cbId,
ev.dataType); // debug confirm the listener callback fires. to be

View File

@ -70,6 +70,17 @@ struct OrbisNpWebApiRequest {
// the libSceHttp request in the send path after the lib-managed CT/Authorization.
std::vector<std::pair<std::string, std::string>> userHeaders;
bool multipart;
// Multipart body assembly (sceNpWebApiSetMultipartContentType / AddMultipartPart /
// SendMultipartRequest). Parts accumulate across sends,the body is framed once all are in.
struct MultipartPart {
std::string rawHeaders; // "<name>: <value>\r\n...Content-Length: <n>\r\n\r\n"
u64 contentLength = 0;
std::string data;
u64 sentSize = 0;
};
std::string multipartContentType; // type name, e.g. "multipart/mixed"
std::string multipartBoundary;
std::vector<MultipartPart> multipartParts;
bool aborted;
bool sent;
u32 requestTimeout;
@ -203,6 +214,8 @@ void startRequestTimer(OrbisNpWebApiRequest* request); // FUN_0100c0d0
void checkRequestTimeout(OrbisNpWebApiRequest* request); // FUN_0100c130
s32 sendRequest(s64 requestId, s32 partIndex, const void* data, u64 dataSize, s8 flag,
OrbisNpWebApiResponseInformationOption* pResponseInformationOption); // FUN_01001c50
s32 setMultipartContentType(s64 requestId, const char* pTypeName, const char* pBoundary);
s32 addMultipartPart(s64 requestId, const OrbisNpWebApiMultipartPartParameter* pParam, s32* pIndex);
s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext,
OrbisNpWebApiRequest* request); // FUN_01001b70
s32 abortRequest(s64 requestId); // FUN_01002c70

View File

@ -222,7 +222,6 @@ s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out) {
{"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib},
{"libSceJson.sprx", nullptr},
{"libSceJson2.sprx", nullptr},
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
{"libSceCesCs.sprx", nullptr},
{"libSceAudiodec.sprx", nullptr},
{"libSceAudiodecCpu.sprx", nullptr},

View File

@ -41,9 +41,7 @@ VdecDecoder::~VdecDecoder() {
s32 VdecDecoder::Decode(const OrbisVideodecInputData& pInputDataIn,
OrbisVideodecFrameBuffer& pFrameBufferInOut,
OrbisVideodecPictureInfo& pPictureInfoOut) {
pPictureInfoOut.thisSize = sizeof(OrbisVideodecPictureInfo);
pPictureInfoOut.isValid = false;
pPictureInfoOut.isErrorPic = true;
if (!pInputDataIn.pAuData) {
return ORBIS_VIDEODEC_ERROR_AU_POINTER;
@ -76,40 +74,37 @@ s32 VdecDecoder::Decode(const OrbisVideodecInputData& pInputDataIn,
av_packet_free(&packet);
return ORBIS_VIDEODEC_ERROR_API_FAIL;
}
int frameCount = 0;
while (true) {
ret = avcodec_receive_frame(mCodecContext, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
LOG_ERROR(Lib_Videodec, "Error receiving frame from decoder: {}", ret);
av_packet_free(&packet);
av_frame_free(&frame);
return ORBIS_VIDEODEC_ERROR_API_FAIL;
}
if (frame->format != AV_PIX_FMT_NV12) {
AVFrame* nv12_frame = ConvertNV12Frame(*frame);
ASSERT(nv12_frame);
av_frame_free(&frame);
frame = nv12_frame;
}
CopyNV12Data((u8*)pFrameBufferInOut.pFrameBuffer, *frame);
pPictureInfoOut.codecType = 0;
pPictureInfoOut.frameWidth = Common::AlignUp((u32)frame->width, 16);
pPictureInfoOut.frameHeight = Common::AlignUp((u32)frame->height, 16);
pPictureInfoOut.framePitch = frame->linesize[0];
pPictureInfoOut.isValid = true;
pPictureInfoOut.isErrorPic = false;
frameCount++;
if (frameCount > 1) {
LOG_WARNING(Lib_Videodec, "We have more than 1 frame");
}
ret = avcodec_receive_frame(mCodecContext, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
av_packet_free(&packet);
av_frame_free(&frame);
return ORBIS_OK;
} else if (ret < 0) {
LOG_ERROR(Lib_Videodec, "Error receiving frame from decoder: {}", ret);
av_packet_free(&packet);
av_frame_free(&frame);
return ORBIS_VIDEODEC_ERROR_API_FAIL;
}
if (frame->format != AV_PIX_FMT_NV12) {
AVFrame* nv12_frame = ConvertNV12Frame(*frame);
ASSERT(nv12_frame);
av_frame_free(&frame);
frame = nv12_frame;
}
CopyNV12Data((u8*)pFrameBufferInOut.pFrameBuffer, *frame);
pPictureInfoOut.codecType = 0;
pPictureInfoOut.frameWidth = Common::AlignUp((u32)frame->width, 16);
pPictureInfoOut.frameHeight = Common::AlignUp((u32)frame->height, 16);
pPictureInfoOut.framePitch = frame->linesize[0];
pPictureInfoOut.isValid = true;
pPictureInfoOut.isErrorPic = false;
pPictureInfoOut.attachedData = pInputDataIn.attachedData;
av_packet_free(&packet);
av_frame_free(&frame);
return ORBIS_OK;
@ -117,9 +112,7 @@ s32 VdecDecoder::Decode(const OrbisVideodecInputData& pInputDataIn,
s32 VdecDecoder::Flush(OrbisVideodecFrameBuffer& pFrameBufferInOut,
OrbisVideodecPictureInfo& pPictureInfoOut) {
pPictureInfoOut.thisSize = sizeof(pPictureInfoOut);
pPictureInfoOut.isValid = false;
pPictureInfoOut.isErrorPic = true;
AVFrame* frame = av_frame_alloc();
if (!frame) {
@ -127,49 +120,49 @@ s32 VdecDecoder::Flush(OrbisVideodecFrameBuffer& pFrameBufferInOut,
return ORBIS_VIDEODEC_ERROR_API_FAIL;
}
int frameCount = 0;
while (true) {
int ret = avcodec_receive_frame(mCodecContext, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
LOG_ERROR(Lib_Videodec, "Error receiving frame from decoder: {}", ret);
av_frame_free(&frame);
return ORBIS_VIDEODEC_ERROR_API_FAIL;
}
if (frame->format != AV_PIX_FMT_NV12) {
AVFrame* nv12_frame = ConvertNV12Frame(*frame);
ASSERT(nv12_frame);
av_frame_free(&frame);
frame = nv12_frame;
}
CopyNV12Data((u8*)pFrameBufferInOut.pFrameBuffer, *frame);
pPictureInfoOut.codecType = 0;
pPictureInfoOut.frameWidth = Common::AlignUp((u32)frame->width, 16);
pPictureInfoOut.frameHeight = Common::AlignUp((u32)frame->height, 16);
pPictureInfoOut.framePitch = frame->linesize[0];
pPictureInfoOut.isValid = true;
pPictureInfoOut.isErrorPic = false;
u32 width = Common::AlignUp((u32)frame->width, 16);
u32 height = Common::AlignUp((u32)frame->height, 16);
pPictureInfoOut.codec.avc.frameCropLeftOffset = u32(frame->crop_left);
pPictureInfoOut.codec.avc.frameCropRightOffset =
u32(frame->crop_right + (width - frame->width));
pPictureInfoOut.codec.avc.frameCropTopOffset = u32(frame->crop_top);
pPictureInfoOut.codec.avc.frameCropBottomOffset =
u32(frame->crop_bottom + (height - frame->height));
// TODO maybe more avc?
if (frameCount > 1) {
LOG_WARNING(Lib_Videodec, "We have more than 1 frame");
}
// avcodec_send_packet with packet set to nullptr results in codec flush.
// This flush can produce multiple frames but we can only return one.
// We cannot skip frames, some games hang if we do so we return only 1st.
// Games can send multiple consecutive flushes, but ffmpeg doesn't work
// this way and returns error. There is no way to make it flush only one
// frame so all we can do is just ignore the result and pray.
avcodec_send_packet(mCodecContext, nullptr);
int ret = avcodec_receive_frame(mCodecContext, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
av_frame_free(&frame);
return ORBIS_OK;
} else if (ret < 0) {
LOG_ERROR(Lib_Videodec, "Error receiving frame from decoder: {}", ret);
av_frame_free(&frame);
return ORBIS_VIDEODEC_ERROR_API_FAIL;
}
if (frame->format != AV_PIX_FMT_NV12) {
AVFrame* nv12_frame = ConvertNV12Frame(*frame);
ASSERT(nv12_frame);
av_frame_free(&frame);
frame = nv12_frame;
}
CopyNV12Data((u8*)pFrameBufferInOut.pFrameBuffer, *frame);
pPictureInfoOut.codecType = 0;
pPictureInfoOut.frameWidth = Common::AlignUp((u32)frame->width, 16);
pPictureInfoOut.frameHeight = Common::AlignUp((u32)frame->height, 16);
pPictureInfoOut.framePitch = frame->linesize[0];
pPictureInfoOut.isValid = true;
pPictureInfoOut.isErrorPic = false;
u32 width = Common::AlignUp((u32)frame->width, 16);
u32 height = Common::AlignUp((u32)frame->height, 16);
pPictureInfoOut.codec.avc.frameCropLeftOffset = u32(frame->crop_left);
pPictureInfoOut.codec.avc.frameCropRightOffset =
u32(frame->crop_right + (width - frame->width));
pPictureInfoOut.codec.avc.frameCropTopOffset = u32(frame->crop_top);
pPictureInfoOut.codec.avc.frameCropBottomOffset =
u32(frame->crop_bottom + (height - frame->height));
av_frame_free(&frame);
return ORBIS_OK;
}

View File

@ -17,6 +17,7 @@
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/sysmodule/sysmodule.h"
#include "core/linker.h"
#include "core/memory.h"
@ -75,14 +76,18 @@ void Linker::Execute(const std::vector<std::string>& args) {
// Map libSceLibcInternal
const auto& libc_internal_path =
EmulatorSettings.GetSysModulesDir() / "libSceLibcInternal.sprx";
bool has_libcinternal = false;
if (std::filesystem::exists(libc_internal_path)) {
LoadModule(libc_internal_path);
has_libcinternal = true;
} else {
// Need to load HLE, LLE isn't present
LOG_INFO(Core_Linker, "Can't Load libSceLibcInternal.sprx switching to HLE");
Libraries::LibcInternal::RegisterLib(&GetHLESymbols());
}
// Relocate all modules
for (const auto& m : m_modules) {
Relocate(m.get());
}
RelocateAllImports();
// Before we can run guest code, we need to properly initialize the heap API and
// libSceLibcInternal. libSceLibcInternal's _malloc_init serves as an additional initialization
@ -90,15 +95,17 @@ void Linker::Execute(const std::vector<std::string>& args) {
heap_api = new HeapAPI{};
static PS4_SYSV_ABI s32 (*malloc_init)() = nullptr;
for (const auto& m : m_modules) {
const auto& mod = m.get();
if (mod->name.contains("libSceLibcInternal.sprx")) {
// Found libSceLibcInternal, now search through function exports.
// Looking for _malloc_init to init libSceLibcInternal properly
// and for all the memory allocating functions, so we can initialize our heap API
for (const auto& sym : mod->export_sym.GetSymbols()) {
if (sym.nid_name.compare("_malloc_init") == 0) {
malloc_init = reinterpret_cast<PS4_SYSV_ABI s32 (*)()>(sym.virtual_address);
if (has_libcinternal) {
for (const auto& m : m_modules) {
const auto& mod = m.get();
if (mod->name.contains("libSceLibcInternal.sprx")) {
// Found libSceLibcInternal, now search through function exports.
// Looking for _malloc_init to init libSceLibcInternal properly
// and for all the memory allocating functions, so we can initialize our heap API
for (const auto& sym : mod->export_sym.GetSymbols()) {
if (sym.nid_name.compare("_malloc_init") == 0) {
malloc_init = reinterpret_cast<PS4_SYSV_ABI s32 (*)()>(sym.virtual_address);
}
}
}
}
@ -139,7 +146,7 @@ void Linker::Execute(const std::vector<std::string>& args) {
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
main_thread.Run([this, module, &args](std::stop_token) {
main_thread.Run([this, module, &args, has_libcinternal](std::stop_token) {
Common::SetCurrentThreadName("Game:Main");
std::set_terminate(Common::Log::Terminate);
@ -153,12 +160,14 @@ void Linker::Execute(const std::vector<std::string>& args) {
}
// Load libSceLibcInternal, run malloc_init.
LoadLibcInternal();
if (has_libcinternal) {
LoadLibcInternal();
if (malloc_init != nullptr) {
// Call _malloc_init
s32 ret = malloc_init();
ASSERT_MSG(ret == 0, "malloc_init failed");
if (malloc_init != nullptr) {
// Call _malloc_init
s32 ret = malloc_init();
ASSERT_MSG(ret == 0, "malloc_init failed");
}
}
// Have libSceSysmodule preload our libraries.
@ -358,7 +367,7 @@ void Linker::Relocate(Module* module) {
if (rel_is_resolved) {
std::memcpy(reinterpret_cast<void*>(rel_virtual_addr), &rel_value, sizeof(rel_value));
} else {
} else if (rel_sym_type == Loader::SymbolType::Function) {
LOG_INFO(Core_Linker, "Function not patched! {}", rel_name);
}
});

View File

@ -0,0 +1,181 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <deque>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <imgui.h>
#include "common/types.h"
#include "core/libraries/np/np_handler.h"
#include "imgui/imgui_layer.h"
#include "imgui/invitation_prompt_layer.h"
#include "imgui/shadnet_notifications_layer.h"
namespace ImGui::InvitationPrompt {
namespace {
enum class State {
Waiting, // shown, awaiting user choice
InFlight, // Accept/Decline running on a worker thread; buttons disabled
};
struct Prompt {
s32 user_id;
std::string invitation_id;
std::string session_id;
std::string from_npid;
State state = State::Waiting;
};
constexpr std::size_t kMaxPrompts = 4; // oldest dropped beyond this
std::mutex g_mutex;
std::deque<Prompt> g_prompts;
class InvitationPromptUI final : public ImGui::Layer {
public:
void Draw() override;
};
InvitationPromptUI g_layer;
void SetState(const std::string& invitation_id, State state) {
std::lock_guard lock(g_mutex);
for (auto& p : g_prompts) {
if (p.invitation_id == invitation_id) {
p.state = state;
}
}
}
void Remove(const std::string& invitation_id) {
std::lock_guard lock(g_mutex);
g_prompts.erase(
std::remove_if(g_prompts.begin(), g_prompts.end(),
[&](const Prompt& p) { return p.invitation_id == invitation_id; }),
g_prompts.end());
}
// Accept/Decline hit the shadNet server (blocking HTTP inside NpHandler), so they run on a
// detached worker thread rather than the render thread. The prompt is marked InFlight until the
// worker finishes, then removed,the outcome is surfaced as a shadNet toast.
void RunAccept(Prompt prompt) {
SetState(prompt.invitation_id, State::InFlight);
std::thread([prompt = std::move(prompt)] {
const bool ok = Libraries::Np::NpHandler::GetInstance().AcceptSessionInvitation(
prompt.user_id, prompt.invitation_id);
Remove(prompt.invitation_id);
if (ok) {
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::Info,
"Accepted " + prompt.from_npid +
"'s invitation, joining session");
} else {
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::Info,
"Could not accept " + prompt.from_npid +
"'s invitation (expired or already used)");
}
}).detach();
}
void RunDecline(Prompt prompt) {
SetState(prompt.invitation_id, State::InFlight);
std::thread([prompt = std::move(prompt)] {
Libraries::Np::NpHandler::GetInstance().DeclineSessionInvitation(prompt.user_id,
prompt.invitation_id);
Remove(prompt.invitation_id);
}).detach();
}
} // namespace
void Push(s32 user_id, std::string invitation_id, std::string session_id, std::string from_npid) {
std::lock_guard lock(g_mutex);
// A re-sent invite replaces the older prompt rather than stacking a duplicate.
g_prompts.erase(
std::remove_if(g_prompts.begin(), g_prompts.end(),
[&](const Prompt& p) { return p.invitation_id == invitation_id; }),
g_prompts.end());
g_prompts.push_back(
{user_id, std::move(invitation_id), std::move(session_id), std::move(from_npid)});
while (g_prompts.size() > kMaxPrompts) {
g_prompts.pop_front();
}
}
void Dismiss(const std::string& invitation_id) {
Remove(invitation_id);
}
void Register() {
ImGui::Layer::AddLayer(&g_layer);
}
void Unregister() {
ImGui::Layer::RemoveLayer(&g_layer);
}
void InvitationPromptUI::Draw() {
std::vector<Prompt> snapshot;
{
std::lock_guard lock(g_mutex);
if (g_prompts.empty()) {
return;
}
snapshot.assign(g_prompts.begin(), g_prompts.end());
}
auto& io = ImGui::GetIO();
const float scale = io.DisplaySize.x / 1920.0f;
const float width = 420.0f * scale;
const float margin = 20.0f * scale;
// Top-center, below the top edge,the transient shadNet toasts live top-right so the two
// don't collide. Mouse-interactive but NoNav so gamepad navigation stays with the game.
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2.0f, margin), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowSize(ImVec2(width, 0.0f), ImGuiCond_Always);
ImGui::SetNextWindowBgAlpha(0.92f);
const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_AlwaysAutoResize;
if (ImGui::Begin("##invitation_prompts", nullptr, flags)) {
bool first = true;
for (const auto& p : snapshot) {
if (!first) {
ImGui::Separator();
}
first = false;
ImGui::TextColored(ImVec4(0.45f, 0.70f, 1.00f, 1.0f), "[Invitation]");
ImGui::SameLine();
ImGui::PushTextWrapPos(0.0f);
ImGui::Text("%s invited you to a session", p.from_npid.c_str());
ImGui::PopTextWrapPos();
const bool busy = p.state == State::InFlight;
ImGui::BeginDisabled(busy);
ImGui::PushID(p.invitation_id.c_str());
if (ImGui::Button(busy ? "Joining..." : "Accept", ImVec2(120.0f * scale, 0.0f))) {
RunAccept(p);
}
ImGui::SameLine();
if (ImGui::Button("Decline", ImVec2(120.0f * scale, 0.0f))) {
RunDecline(p);
}
ImGui::PopID();
ImGui::EndDisabled();
}
}
ImGui::End();
}
} // namespace ImGui::InvitationPrompt

View File

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "common/types.h"
namespace ImGui::InvitationPrompt {
// Emulator-side stand-in for the PS4 system software UI ("ShellUI") invitation notification.
//
// ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION fires only after the user
// "explicitly performs an action that joins from an invitation... using invitation dialog or the
// system software UI". Titles that don't open the invitation dialog (RECV) themselves rely
// entirely on the system software UI, which doesn't exist in the emulator. This layer fills that
// role: NpHandler queues a prompt when a sessionInvitation push arrives, the user Accepts or
// Declines here, and Accept routes through NpHandler::AcceptSessionInvitation - the same path the
// RECV dialog uses - which posts the join event and consumes the invite server-side.
// Queue an accept/decline prompt for a received session invitation. Thread-safe; deduplicates on
// invitation_id (a re-pushed invite replaces the older prompt).
void Push(s32 user_id, std::string invitation_id, std::string session_id, std::string from_npid);
// Drop a queued prompt (e.g. the invite was consumed through the RECV dialog instead).
// Thread-safe,no-op if not present.
void Dismiss(const std::string& invitation_id);
// Add/remove the overlay layer. Call Register() once after ImGui init and Unregister() on
// teardown.
void Register();
void Unregister();
} // namespace ImGui::InvitationPrompt

View File

@ -166,6 +166,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_FLOOR_F32(inst);
case Opcode::V_EXP_F32:
return V_EXP_F32(inst);
case Opcode::V_LOG_CLAMP_F32:
return V_LOG_F32(inst);
case Opcode::V_LOG_F32:
return V_LOG_F32(inst);
case Opcode::V_RCP_F32:

View File

@ -74,11 +74,14 @@ enum class CommandType : u16 {
SearchRoom = 16,
RequestSignalingInfos = 17,
ContextStop = 18,
SetUserInfo = 19,
SetRoomDataInternal = 20,
SetRoomDataExternal = 21,
KickoutRoomMember = 22,
GetWorldInfoList = 23,
// 24-29 reserved for future matchmaking commands
GetRoomDataExternalList = 24,
GetUserInfoList = 25,
GetRoomMemberDataExternalList = 26,
GetBoardInfos = 30,
RecordScore = 31,
RecordScoreData = 32,

View File

@ -260,6 +260,15 @@ message MatchingRoomMemberData {
uint32 platform = 14;
}
message MatchingRoomMemberDataExternal {
string npid = 1;
uint32 member_id = 2;
uint64 account_id = 3;
uint32 platform = 4;
uint64 join_date = 5;
uint32 role = 6;
}
message MatchingRoomDataExternal {
uint32 max_slot = 1;
uint32 cur_members = 2;
@ -279,6 +288,8 @@ message MatchingRoomDataExternal {
repeated MatchingBinAttr external_search_bin_attrs = 16;
repeated MatchingBinAttr external_bin_attrs = 17;
string owner_npid = 18;
uint64 owner_account_id = 19;
uint32 owner_platform = 20;
}
message CreateJoinRoomResponse {
@ -433,6 +444,47 @@ message SearchRoomReply {
uint32 range_result = 4;
}
message GetRoomDataExternalListRequest {
repeated uint64 room_ids = 1;
repeated uint32 attr_ids = 2;
}
message GetRoomDataExternalListReply {
repeated MatchingRoomDataExternal rooms = 1;
}
message GetRoomMemberDataExternalListRequest {
uint64 room_id = 1;
}
message GetRoomMemberDataExternalListReply {
repeated MatchingRoomMemberDataExternal members = 1;
}
message MatchingUserInfo {
string npid = 1;
uint64 account_id = 2;
uint32 platform = 3;
repeated MatchingBinAttr user_bin_attrs = 4;
}
message SetUserInfoRequest {
uint32 server_id = 1;
repeated MatchingBinAttr user_bin_attrs = 2;
}
message SetUserInfoReply {}
message GetUserInfoListRequest {
repeated string npids = 1;
repeated uint32 attr_ids = 2;
int32 option = 3;
}
message GetUserInfoListReply {
repeated MatchingUserInfo users = 1;
}
message MatchingWorld {
uint32 world_id = 1;
uint32 lobbies_num = 2;

View File

@ -11,6 +11,7 @@
#include "core/emulator_settings.h"
#include "core/libraries/system/systemservice.h"
#include "imgui/friends_layer.h"
#include "imgui/invitation_prompt_layer.h"
#include "imgui/notifications_layer.h"
#include "imgui/renderer/imgui_core.h"
#include "imgui/renderer/imgui_impl_vulkan.h"
@ -525,9 +526,11 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_
ImGui::Layer::AddLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
ImGui::Friends::Register();
ImGui::ShadNetNotify::Register();
ImGui::InvitationPrompt::Register();
}
Presenter::~Presenter() {
ImGui::InvitationPrompt::Unregister();
ImGui::ShadNetNotify::Unregister();
ImGui::Friends::Unregister();
ImGui::Layer::RemoveLayer(Common::Singleton<Core::Devtools::Layer>::Instance());