mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-07-10 10:14:44 -06:00
Merge branch 'shadps4-emu:main' into fix/windows-condvar-stale-wake-guard
This commit is contained in:
commit
745304bd91
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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, ×tampNS);
|
||||
|
||||
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,
|
||||
|
||||
448
src/core/libraries/invitation_dialog/invitation_dialog.cpp
Normal file
448
src/core/libraries/invitation_dialog/invitation_dialog.cpp
Normal 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
|
||||
183
src/core/libraries/invitation_dialog/invitation_dialog.h
Normal file
183
src/core/libraries/invitation_dialog/invitation_dialog.h
Normal 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
|
||||
277
src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp
Normal file
277
src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp
Normal 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
|
||||
65
src/core/libraries/invitation_dialog/invitation_dialog_ui.h
Normal file
65
src/core/libraries/invitation_dialog/invitation_dialog_ui.h
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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(©, &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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
181
src/imgui/invitation_prompt_layer.cpp
Normal file
181
src/imgui/invitation_prompt_layer.cpp
Normal 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
|
||||
35
src/imgui/invitation_prompt_layer.h
Normal file
35
src/imgui/invitation_prompt_layer.h
Normal 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
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
|
||||
Loading…
Reference in New Issue
Block a user