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

This commit is contained in:
Valdis Bogdāns 2026-07-09 01:08:22 +03:00 committed by GitHub
commit 646a45d36b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 3944 additions and 703 deletions

View File

@ -649,8 +649,6 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_tus.h
src/core/libraries/np/trophy_ui.cpp
src/core/libraries/np/trophy_ui.h
src/core/libraries/np/np_web_api2.cpp
src/core/libraries/np/np_web_api2.h
src/core/libraries/np/np_party.cpp
src/core/libraries/np/np_party.h
src/core/libraries/np/np_auth.cpp
@ -685,6 +683,14 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_web_api/np_web_api.h
src/core/libraries/np/np_web_api/np_web_api_internal.cpp
src/core/libraries/np/np_web_api/np_web_api_internal.h
src/core/libraries/np/np_web_api2/np_web_api2.cpp
src/core/libraries/np/np_web_api2/np_web_api2.h
src/core/libraries/np/np_web_api2/np_web_api2_internal.cpp
src/core/libraries/np/np_web_api2/np_web_api2_internal.h
src/core/libraries/np/np_web_api2/np_web_api2_context.cpp
src/core/libraries/np/np_web_api2/np_web_api2_context.h
src/core/libraries/np/np_web_api2/np_web_api2_push_event.cpp
src/core/libraries/np/np_web_api2/np_web_api2_push_event.h
)
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp

View File

@ -49,7 +49,7 @@
#include "core/libraries/np/np_trophy.h"
#include "core/libraries/np/np_tus.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
#include "core/libraries/np/np_web_api2.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/playgo/playgo.h"
#include "core/libraries/playgo/playgo_dialog.h"

View File

@ -4,293 +4,359 @@
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/http2.h"
#include <map>
namespace Libraries::Http2 {
int PS4_SYSV_ABI _Z5dummyv() {
std::map<s32, s32> requests_to_connections{};
s32 PS4_SYSV_ABI sceHttp2AbortRequest(s32 req_id) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpAbortRequest(req_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to get abort HTTP request, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2AddCookie() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2AbortRequest() {
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
const char* value, u32 mode) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpAddRequestHeader(template_or_req_id, name, value, mode);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to add HTTP response header, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2AuthCacheFlush() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2AddCookie() {
s32 PS4_SYSV_ABI sceHttp2CookieExport() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2AddRequestHeader() {
s32 PS4_SYSV_ABI sceHttp2CookieFlush() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2AuthCacheFlush() {
s32 PS4_SYSV_ABI sceHttp2CookieImport() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2CookieExport() {
s32 PS4_SYSV_ABI sceHttp2CreateCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2CookieFlush() {
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
u64 content_length) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
// Http1 has a connection thing to go through first
s32 conn_id = Libraries::Http::sceHttpCreateConnectionWithURL(tmpl_id, url, true);
if (conn_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to create HTTP connection, error = {:#x}", conn_id);
return conn_id;
}
s32 req_id =
Libraries::Http::sceHttpCreateRequestWithURL2(conn_id, method, url, content_length);
if (req_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to create HTTP request, error = {:#x}", req_id);
return req_id;
}
requests_to_connections[req_id] = conn_id;
return req_id;
}
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, const char* user_agent, s32 http_ver,
s32 auto_proxy_conf) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 tmpl_id =
Libraries::Http::sceHttpCreateTemplate(ctx_id, user_agent, http_ver, auto_proxy_conf);
if (tmpl_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to create HTTP template, error = {:#x}", tmpl_id);
}
return tmpl_id;
}
s32 PS4_SYSV_ABI sceHttp2DeleteCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2CookieImport() {
s32 PS4_SYSV_ABI sceHttp2DeleteRequest(s32 req_id) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpDeleteRequest(req_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to delete HTTP request, error = {:#x}", result);
return result;
}
s32 conn_id = requests_to_connections[req_id];
result = Libraries::Http::sceHttpDeleteConnection(conn_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to delete HTTP connection, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate(s32 tmpl_id) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpDeleteTemplate(tmpl_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to delete HTTP template, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpGetAllResponseHeaders(req_id, header, header_size);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to get HTTP response headers, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2GetAuthEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2CreateCookieBox() {
s32 PS4_SYSV_ABI sceHttp2GetAutoRedirect() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2CreateRequestWithURL() {
s32 PS4_SYSV_ABI sceHttp2GetCookie() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2CreateTemplate() {
s32 PS4_SYSV_ABI sceHttp2GetCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2DeleteCookieBox() {
s32 PS4_SYSV_ABI sceHttp2GetCookieStats() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2DeleteRequest() {
s32 PS4_SYSV_ABI sceHttp2GetMemoryPoolStats() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2DeleteTemplate() {
s32 PS4_SYSV_ABI sceHttp2GetResponseContentLength() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetAllResponseHeaders() {
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 req_id, s32* status_code) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
s32 result = Libraries::Http::sceHttpGetStatusCode(req_id, status_code);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to get HTTP status code, error = {:#x}", result);
}
return result;
}
int PS4_SYSV_ABI sceHttp2GetAuthEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetAutoRedirect() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetCookie() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetCookieStats() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetMemoryPoolStats() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetResponseContentLength() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2GetStatusCode() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2Init(int net_id, int ssl_id, size_t pool_size, int max_requests) {
s32 PS4_SYSV_ABI sceHttp2Init(s32 net_id, s32 ssl_id, u64 pool_size, s32 max_requests) {
LOG_ERROR(Lib_Http2, "(DUMMY) called");
static int id = 0;
return ++id;
s32 ctx_id = Libraries::Http::sceHttpInit(net_id, ssl_id, pool_size);
if (ctx_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to init HTTP context, error = {:#x}", ctx_id);
}
return ctx_id;
}
int PS4_SYSV_ABI sceHttp2ReadData() {
s32 PS4_SYSV_ABI sceHttp2ReadData(s32 req_id, void* data, u64 size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpReadData(req_id, data, size);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to read HTTP data, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2ReadDataAsync() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2ReadDataAsync() {
s32 PS4_SYSV_ABI sceHttp2RedirectCacheFlush() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2RedirectCacheFlush() {
s32 PS4_SYSV_ABI sceHttp2RemoveRequestHeader() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2RemoveRequestHeader() {
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpSendRequest(req_id, data, size);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to send HTTP request, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2SendRequestAsync() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SendRequest() {
s32 PS4_SYSV_ABI sceHttp2SetAuthEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SendRequestAsync() {
s32 PS4_SYSV_ABI sceHttp2SetAuthInfoCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetAuthEnabled() {
s32 PS4_SYSV_ABI sceHttp2SetAutoRedirect() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetAuthInfoCallback() {
s32 PS4_SYSV_ABI sceHttp2SetConnectionWaitTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetAutoRedirect() {
s32 PS4_SYSV_ABI sceHttp2SetConnectTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetConnectionWaitTimeOut() {
s32 PS4_SYSV_ABI sceHttp2SetCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetConnectTimeOut() {
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxNum() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetCookieBox() {
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxNumPerDomain() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetCookieMaxNum() {
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxSize() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetCookieMaxNumPerDomain() {
s32 PS4_SYSV_ABI sceHttp2SetCookieRecvCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetCookieMaxSize() {
s32 PS4_SYSV_ABI sceHttp2SetCookieSendCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetCookieRecvCallback() {
s32 PS4_SYSV_ABI sceHttp2SetInflateGZIPEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetCookieSendCallback() {
s32 PS4_SYSV_ABI sceHttp2SetMinSslVersion() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetInflateGZIPEnabled() {
s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCallback cb_func,
void* user_arg) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetMinSslVersion() {
s32 PS4_SYSV_ABI sceHttp2SetRecvTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetPreSendCallback() {
s32 PS4_SYSV_ABI sceHttp2SetRedirectCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetRecvTimeOut() {
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpSetRequestContentLength(req_id, content_length);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to set HTTP request content length, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2SetResolveRetry() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetRedirectCallback() {
s32 PS4_SYSV_ABI sceHttp2SetResolveTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetRequestContentLength() {
s32 PS4_SYSV_ABI sceHttp2SetSendTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetResolveRetry() {
s32 PS4_SYSV_ABI sceHttp2SetSslCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetResolveTimeOut() {
s32 PS4_SYSV_ABI sceHttp2SetTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetSendTimeOut() {
s32 PS4_SYSV_ABI sceHttp2SslDisableOption() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetSslCallback() {
s32 PS4_SYSV_ABI sceHttp2SslEnableOption() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SetTimeOut() {
s32 PS4_SYSV_ABI sceHttp2Term() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SslDisableOption() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2SslEnableOption() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2Term() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttp2WaitAsync() {
s32 PS4_SYSV_ABI sceHttp2WaitAsync() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("AS45QoYHjc4", "libSceHttp2", 1, "libSceHttp2", _Z5dummyv);
LIB_FUNCTION("IZ-qjhRqvjk", "libSceHttp2", 1, "libSceHttp2", sceHttp2AbortRequest);
LIB_FUNCTION("flPxnowtvWY", "libSceHttp2", 1, "libSceHttp2", sceHttp2AddCookie);
LIB_FUNCTION("nrPfOE8TQu0", "libSceHttp2", 1, "libSceHttp2", sceHttp2AddRequestHeader);

View File

@ -4,6 +4,7 @@
#pragma once
#include "common/types.h"
#include "core/libraries/network/ssl2.h"
namespace Core::Loader {
class SymbolsResolver;
@ -11,62 +12,38 @@ class SymbolsResolver;
namespace Libraries::Http2 {
int PS4_SYSV_ABI _Z5dummyv();
int PS4_SYSV_ABI sceHttp2AbortRequest();
int PS4_SYSV_ABI sceHttp2AddCookie();
int PS4_SYSV_ABI sceHttp2AddRequestHeader();
int PS4_SYSV_ABI sceHttp2AuthCacheFlush();
int PS4_SYSV_ABI sceHttp2CookieExport();
int PS4_SYSV_ABI sceHttp2CookieFlush();
int PS4_SYSV_ABI sceHttp2CookieImport();
int PS4_SYSV_ABI sceHttp2CreateCookieBox();
int PS4_SYSV_ABI sceHttp2CreateRequestWithURL();
int PS4_SYSV_ABI sceHttp2CreateTemplate();
int PS4_SYSV_ABI sceHttp2DeleteCookieBox();
int PS4_SYSV_ABI sceHttp2DeleteRequest();
int PS4_SYSV_ABI sceHttp2DeleteTemplate();
int PS4_SYSV_ABI sceHttp2GetAllResponseHeaders();
int PS4_SYSV_ABI sceHttp2GetAuthEnabled();
int PS4_SYSV_ABI sceHttp2GetAutoRedirect();
int PS4_SYSV_ABI sceHttp2GetCookie();
int PS4_SYSV_ABI sceHttp2GetCookieBox();
int PS4_SYSV_ABI sceHttp2GetCookieStats();
int PS4_SYSV_ABI sceHttp2GetMemoryPoolStats();
int PS4_SYSV_ABI sceHttp2GetResponseContentLength();
int PS4_SYSV_ABI sceHttp2GetStatusCode();
int PS4_SYSV_ABI sceHttp2Init(int net_id, int ssl_id, size_t pool_size, int max_requests);
int PS4_SYSV_ABI sceHttp2ReadData();
int PS4_SYSV_ABI sceHttp2ReadDataAsync();
int PS4_SYSV_ABI sceHttp2RedirectCacheFlush();
int PS4_SYSV_ABI sceHttp2RemoveRequestHeader();
int PS4_SYSV_ABI sceHttp2SendRequest();
int PS4_SYSV_ABI sceHttp2SendRequestAsync();
int PS4_SYSV_ABI sceHttp2SetAuthEnabled();
int PS4_SYSV_ABI sceHttp2SetAuthInfoCallback();
int PS4_SYSV_ABI sceHttp2SetAutoRedirect();
int PS4_SYSV_ABI sceHttp2SetConnectionWaitTimeOut();
int PS4_SYSV_ABI sceHttp2SetConnectTimeOut();
int PS4_SYSV_ABI sceHttp2SetCookieBox();
int PS4_SYSV_ABI sceHttp2SetCookieMaxNum();
int PS4_SYSV_ABI sceHttp2SetCookieMaxNumPerDomain();
int PS4_SYSV_ABI sceHttp2SetCookieMaxSize();
int PS4_SYSV_ABI sceHttp2SetCookieRecvCallback();
int PS4_SYSV_ABI sceHttp2SetCookieSendCallback();
int PS4_SYSV_ABI sceHttp2SetInflateGZIPEnabled();
int PS4_SYSV_ABI sceHttp2SetMinSslVersion();
int PS4_SYSV_ABI sceHttp2SetPreSendCallback();
int PS4_SYSV_ABI sceHttp2SetRecvTimeOut();
int PS4_SYSV_ABI sceHttp2SetRedirectCallback();
int PS4_SYSV_ABI sceHttp2SetRequestContentLength();
int PS4_SYSV_ABI sceHttp2SetResolveRetry();
int PS4_SYSV_ABI sceHttp2SetResolveTimeOut();
int PS4_SYSV_ABI sceHttp2SetSendTimeOut();
int PS4_SYSV_ABI sceHttp2SetSslCallback();
int PS4_SYSV_ABI sceHttp2SetTimeOut();
int PS4_SYSV_ABI sceHttp2SslDisableOption();
int PS4_SYSV_ABI sceHttp2SslEnableOption();
int PS4_SYSV_ABI sceHttp2Term();
int PS4_SYSV_ABI sceHttp2WaitAsync();
enum OrbisHttp2HttpVersion : u32 {
ORBIS_HTTP2_VERSION_1_0 = 1,
ORBIS_HTTP2_VERSION_1_1 = 2,
ORBIS_HTTP2_VERSION_2_0 = 3,
};
// TODO: Figure out the whole struct. What's here is based on libSceNpWebApi2 use.
struct OrbisHttp2PreSendCallbackData {
void* unk;
void* cert;
};
using OrbisHttp2PreSendCallback = PS4_SYSV_ABI s32 (*)(s32 request_id, s32 ssl_id,
OrbisHttp2PreSendCallbackData* data,
void* user_arg);
s32 PS4_SYSV_ABI sceHttp2AbortRequest(s32 req_id);
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
const char* value, u32 mode);
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
u64 content_length);
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, const char* user_agent, s32 http_ver,
s32 auto_proxy_conf);
s32 PS4_SYSV_ABI sceHttp2DeleteRequest(s32 req_id);
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate(s32 tmpl_id);
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size);
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 request_id, s32* status_code);
s32 PS4_SYSV_ABI sceHttp2Init(s32 net_id, s32 ssl_id, u64 pool_size, s32 max_requests);
s32 PS4_SYSV_ABI sceHttp2ReadData(s32 req_id, void* data, u64 size);
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size);
s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCallback cb_func,
void* user_arg);
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Http2

View File

@ -30,6 +30,11 @@ UPnPClient::~UPnPClient() {
}
void UPnPClient::Start() {
if (!m_p2p_features_enabled.load()) {
LOG_INFO(Lib_Net, "UPNP: skipped because Matching2 is disabled");
return;
}
bool expected = false;
if (!m_started.compare_exchange_strong(expected, true))
return;
@ -37,6 +42,10 @@ void UPnPClient::Start() {
m_thread = std::thread(&UPnPClient::DiscoverThread, this);
}
void UPnPClient::SetP2PFeaturesEnabled(bool enabled) {
m_p2p_features_enabled.store(enabled);
}
bool UPnPClient::WaitReady(int timeout_ms) {
std::unique_lock lock(m_mutex);
m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { return m_done.load(); });
@ -106,6 +115,9 @@ void UPnPClient::DiscoverThread() {
}
void UPnPClient::AddMapping(u16 port) {
if (!m_p2p_features_enabled.load())
return;
if (!m_available.load())
return;

View File

@ -21,6 +21,7 @@ public:
// Kick off async IGD discovery. Safe to call multiple times — only the first call has effect.
void Start();
void SetP2PFeaturesEnabled(bool enabled);
// Block until discovery completes or timeout_ms elapses. Returns true if IGD was found.
bool WaitReady(int timeout_ms);
@ -48,6 +49,7 @@ private:
void DiscoverThread();
std::atomic<bool> m_started{false};
std::atomic<bool> m_p2p_features_enabled{true};
std::atomic<bool> m_done{false};
std::atomic<bool> m_available{false};
std::atomic<u32> m_external_ip_nbo{0};

View File

@ -390,6 +390,17 @@ s32 PS4_SYSV_ABI sceNpIntIsValidOnlineId(const OrbisNpOnlineId* id) {
return 1;
}
void PS4_SYSV_ABI sceNpGetSdkVersion(char* version_buf) {
LOG_DEBUG(Lib_NpCommon, "called");
// Real library has no parameter checks, so I guess this is purely for internal use?
Libraries::Kernel::SwVersionStruct sw_version{};
sw_version.struct_size = 0x28;
Libraries::Kernel::sceKernelGetSystemSwVersion(&sw_version);
u32 sw_hex = sw_version.hex_representation;
snprintf(version_buf, 8, "%d.%02d", ((sw_hex >> 0x18) & 0xf) + ((sw_hex >> 0x1c) * 10),
((sw_hex >> 0x10) & 0xf) + (((sw_hex >> 0x14) & 0xf) * 10));
}
s32 PS4_SYSV_ABI sceNpCalloutInitCtx(OrbisNpCalloutContext* callout_ctx, const char* name,
u64 stack_size, s32 priority, u64 affinity_mask) {
LOG_DEBUG(Lib_NpCommon, "ctx={:p} name={:p} stack={:#x} priority={} affinity={:#x}",
@ -600,6 +611,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("ss2xO9IJxKQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondTimedwait);
LIB_FUNCTION("uEwag-0YZPc", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexInit);
LIB_FUNCTION("uMJFOA62mVU", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondSignal);
LIB_FUNCTION("Pglk7zFj0DI", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetSdkVersion);
};
} // namespace Libraries::Np::NpCommon

View File

@ -62,6 +62,7 @@ u32 PS4_SYSV_ABI sceNpJoinThread(Libraries::Kernel::PthreadT thread, void** ret)
s32 PS4_SYSV_ABI sceNpGetSystemClockUsec(s64* usec);
s32 PS4_SYSV_ABI sceNpGetPlatformType(const OrbisNpId* np_id);
s32 PS4_SYSV_ABI sceNpIntIsValidOnlineId(const OrbisNpOnlineId* id);
void PS4_SYSV_ABI sceNpGetSdkVersion(char* version_buf);
s32 PS4_SYSV_ABI sceNpCalloutInitCtx(OrbisNpCalloutContext* ctx, const char* name, u64 stack_size,
s32 priority, u64 affinity_mask);
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,

View File

@ -553,25 +553,25 @@ constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND = 0x80553406;
constexpr int ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN = 0x80553407;
constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER = 0x80553408;
constexpr int ORBIS_NP_WEBAPI2_ERROR_ABORTED = 0x80553409;
constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_ALREADY_EXIST = 0x8055340a;
constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b;
constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c;
constexpr int ORBIS_NP_WEBAPI2_HANDLE_NOT_FOUND = 0x8055340d;
constexpr int ORBIS_NP_WEBAPI2_SIGNED_IN_USER_NOT_FOUND = 0x8055340e;
constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_BUSY = 0x8055340f;
constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_BUSY = 0x80553410;
constexpr int ORBIS_NP_WEBAPI2_REQUEST_BUSY = 0x80553411;
constexpr int ORBIS_NP_WEBAPI2_INVALID_HTTP_STATUS_CODE = 0x80553412;
constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_HTTP_HEADER = 0x80553413;
constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_FUNCTION_CALL = 0x80553414;
constexpr int ORBIS_NP_WEBAPI2_MULTIPART_PART_NOT_FOUND = 0x80553415;
constexpr int ORBIS_NP_WEBAPI2_PARAMETER_TOO_LONG = 0x80553416;
constexpr int ORBIS_NP_WEBAPI2_HANDLE_BUSY = 0x80553417;
constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_MAX = 0x80553418;
constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_MAX = 0x80553419;
constexpr int ORBIS_NP_WEBAPI2_AFTER_SEND = 0x8055341a;
constexpr int ORBIS_NP_WEBAPI2_TIMEOUT = 0x8055341b;
constexpr int ORBIS_NP_WEBAPI2_PUSH_CONTEXT_NOT_FOUND = 0x8055341c;
constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_ALREADY_EXIST = 0x8055340a;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c;
constexpr int ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND = 0x8055340d;
constexpr int ORBIS_NP_WEBAPI2_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x8055340e;
constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_BUSY = 0x8055340f;
constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_BUSY = 0x80553410;
constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_BUSY = 0x80553411;
constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_HTTP_STATUS_CODE = 0x80553412;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_HTTP_HEADER = 0x80553413;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_FUNCTION_CALL = 0x80553414;
constexpr int ORBIS_NP_WEBAPI2_ERROR_MULTIPART_PART_NOT_FOUND = 0x80553415;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PARAMETER_TOO_LONG = 0x80553416;
constexpr int ORBIS_NP_WEBAPI2_ERROR_HANDLE_BUSY = 0x80553417;
constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_MAX = 0x80553418;
constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_MAX = 0x80553419;
constexpr int ORBIS_NP_WEBAPI2_ERROR_AFTER_SEND = 0x8055341a;
constexpr int ORBIS_NP_WEBAPI2_ERROR_TIMEOUT = 0x8055341b;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PUSH_CONTEXT_NOT_FOUND = 0x8055341c;
// NP Session Management Client (0x80553600 - 0x8055361e)
constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_ALREADY_INITIALIZED = 0x80553600;

View File

@ -182,9 +182,6 @@ bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, cons
// Seed the current Appear-Offline preference so the login packet carries it (the send
// is suppressed pre-auth; it just caches on the client).
client->SetAppearOffline(m_appear_offline.load());
if (EmulatorSettings.IsUPnPEnabled()) {
Net::UPnPClient::Instance().Start();
}
client->Start(host, port, npid, password, token);
const ShadNet::ShadNetState conn_state = client->WaitForConnection();
@ -206,12 +203,17 @@ bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, cons
LOG_INFO(NpHandler, "user_id={} signed in npid='{}' accountId={}", user_id, npid,
client->GetUserId());
Net::UPnPClient::Instance().SetP2PFeaturesEnabled(client->IsMatching2Enabled());
if (client->IsMatching2Enabled() && EmulatorSettings.IsUPnPEnabled()) {
Net::UPnPClient::Instance().Start();
}
NpMatching2::SetMmShadNetClient(client, host, port);
// Build OrbisNpId
{
OrbisNpId np_id{};
strncpy(np_id.handle.data, npid.c_str(), sizeof(np_id.handle.data) - 1);
SetNpId(np_id, npid);
std::lock_guard lock(m_mutex_clients);
m_np_ids[user_id] = np_id;
m_clients[user_id] = std::move(client);
@ -704,11 +706,11 @@ void NpHandler::OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEv
ev.data = n.data;
if (!n.fromNpid.empty()) {
ev.hasFrom = true;
std::strncpy(ev.fromOnlineId.data, n.fromNpid.c_str(), sizeof(ev.fromOnlineId.data) - 1);
SetNpOnlineId(ev.fromOnlineId, n.fromNpid);
}
if (!n.toNpid.empty()) {
ev.hasTo = true;
std::strncpy(ev.toOnlineId.data, n.toNpid.c_str(), sizeof(ev.toOnlineId.data) - 1);
SetNpOnlineId(ev.toOnlineId, n.toNpid);
}
ev.extdData = n.extdData; // extended-data (key,value) pairs -> dispatched as pExtdData
NpWebApi::EnqueuePushEvent(ev);
@ -1883,7 +1885,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 <= 26) {
if (cmd_val >= 100 && cmd_val <= 200) {
NpMatching2::OnMatchingReply(cmd, pkt_id, error, body);
} else {
OnScoreReply(user_id, cmd, pkt_id, error, body);

View File

@ -3,6 +3,7 @@
#include <chrono>
#include <cstring>
#include <memory>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
@ -245,7 +246,11 @@ int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) {
return rc;
}
MmContextStop(ctxId);
const s32 submit_rc = MmContextStop(ctxId);
if (submit_rc != ORBIS_OK) {
ContextManager::Instance().CompleteStop(ctxId);
return submit_rc;
}
return ORBIS_OK;
}
@ -468,9 +473,10 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId,
}
StoreRequestCallback(ctx, requestOpt);
*requestId = AllocRequestId();
LOG_WARNING(Lib_NpMatching2, "not implemented");
return ORBIS_OK;
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
return MmSendRoomMessage(ctxId, reqId,
*static_cast<OrbisNpMatching2SendRoomMessageRequest*>(request));
}
int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(
@ -570,8 +576,7 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont
if (member_it != room_it->second.members.end()) {
pi.addr = member_it->second.addr;
pi.port = member_it->second.port;
std::strncpy(pi.online_id.data, member_it->second.np_id.handle.data,
sizeof(pi.online_id.data) - 1);
pi.online_id = member_it->second.np_id.handle;
}
peer_it = ctx->peers.emplace(memberId, pi).first;
}
@ -750,8 +755,9 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
ev.req_id = reqId;
ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_INTERNAL;
ev.error_code = 0;
ev.request_cb = ctx->default_request_callback;
ev.request_cb_arg = ctx->default_request_callback_arg;
const RequestCallbackInfo request_cb = ConsumeRequestCallback(ctx);
ev.request_cb = request_cb.callback;
ev.request_cb_arg = request_cb.arg;
ev.request_data = request_data;
ScheduleEvent(std::move(ev));
return ORBIS_OK;
@ -971,9 +977,10 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct
const OrbisNpMatching2RequestId request_id = AllocRequestId();
*reqId = request_id;
const bool room_found = ctx->room_cache.find(request->roomId) != ctx->room_cache.end();
void* request_data =
room_found ? BuildSignalingGetPingInfoPayload(*ctx, request->roomId) : nullptr;
auto request_payload_owner = std::make_shared<CallbackPayload>();
ctx->request_payload_override = request_payload_owner.get();
void* request_data = BuildSignalingGetPingInfoPayload(*ctx, request->roomId);
ctx->request_payload_override = nullptr;
PendingEvent ev{};
ev.type = PendingEvent::REQUEST_CB;
@ -981,10 +988,12 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct
ev.fire_at = std::chrono::steady_clock::now();
ev.req_id = request_id;
ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO;
ev.error_code = room_found ? ORBIS_OK : ORBIS_NP_MATCHING2_ERROR_ROOM_NOT_FOUND;
ev.request_cb = ctx->default_request_callback;
ev.request_cb_arg = ctx->default_request_callback_arg;
ev.error_code = ORBIS_OK;
const RequestCallbackInfo request_cb = ConsumeRequestCallback(ctx);
ev.request_cb = request_cb.callback;
ev.request_cb_arg = request_cb.arg;
ev.request_data = request_data;
ev.request_payload_owner = std::move(request_payload_owner);
ScheduleEvent(std::move(ev));
return ORBIS_OK;
}

View File

@ -20,6 +20,10 @@ NpMatching2State g_state;
namespace {
CallbackPayload& RequestPayload(ContextObject& ctx) {
return ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload;
}
u32 IpStringToAddr(std::string_view ip) {
u32 a, b, c, d;
if (std::sscanf(std::string(ip).c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4) {
@ -60,7 +64,7 @@ void ReserveExternalRoomPayloadStorage(CallbackPayload& p, const Reply& resp) {
void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx,
const shadnet::CreateJoinRoomResponse& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
const auto& rd = resp.room_data();
p.room_groups.resize(rd.groups_size());
@ -172,7 +176,7 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx,
mc.join_date = m.join_date();
mc.addr = IpStringToAddr(m.addr());
mc.port = Libraries::Net::sceNetHtons(static_cast<u16>(m.port()));
std::strncpy(mc.np_id.handle.data, m.npid().c_str(), sizeof(mc.np_id.handle.data) - 1);
SetNpId(mc.np_id, m.npid());
mc.account_id = static_cast<Libraries::Np::OrbisNpAccountId>(m.account_id());
mc.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(m.platform());
for (const auto& a : m.bin_attrs_internal()) {
@ -186,7 +190,7 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx,
peer.member_id = mc.member_id;
peer.addr = mc.addr;
peer.port = mc.port;
std::strncpy(peer.online_id.data, m.npid().c_str(), sizeof(peer.online_id.data) - 1);
SetNpOnlineId(peer.online_id, m.npid());
ctx.peers[mc.member_id] = peer;
}
}
@ -227,7 +231,7 @@ OrbisNpMatching2RoomMemberBinAttrInternal* AppendMemberBinAttrs(
}
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
BuildCreateJoinRoomPayloadCommon(ctx, resp);
@ -240,7 +244,7 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
dst = OrbisNpMatching2RoomMemberDataInternal{};
dst.next = (i + 1 < member_count) ? &p.member_data[i + 1] : nullptr;
dst.joinDate = m.join_date();
std::strncpy(dst.npId.handle.data, m.npid().c_str(), sizeof(dst.npId.handle.data) - 1);
SetNpId(dst.npId, m.npid());
dst.memberId = static_cast<OrbisNpMatching2RoomMemberId>(m.member_id());
dst.teamId = static_cast<OrbisNpMatching2TeamId>(m.team_id());
dst.natType = static_cast<OrbisNpMatching2NatType>(m.nat_type());
@ -274,7 +278,7 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
}
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
BuildCreateJoinRoomPayloadCommon(ctx, resp);
@ -289,7 +293,7 @@ void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinR
dst.joinDateTicks.tick = m.join_date();
dst.user.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(m.account_id());
dst.user.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(m.platform());
std::strncpy(dst.onlineId.data, m.npid().c_str(), sizeof(dst.onlineId.data) - 1);
SetNpOnlineId(dst.onlineId, m.npid());
dst.memberId = static_cast<OrbisNpMatching2RoomMemberId>(m.member_id());
dst.teamId = static_cast<OrbisNpMatching2TeamId>(m.team_id());
dst.natType = static_cast<OrbisNpMatching2NatType>(m.nat_type());
@ -322,7 +326,7 @@ void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinR
}
void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
p.leave_room_response = std::make_unique<OrbisNpMatching2LeaveRoomResponse>();
@ -341,7 +345,7 @@ void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& r
}
void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
p.world_list.resize(resp.worlds_size());
@ -369,7 +373,7 @@ void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldIn
}
void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
@ -396,8 +400,7 @@ void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply&
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);
SetNpId(npid, r.owner_npid());
dst.ownerNpId = &npid;
}
@ -474,7 +477,7 @@ void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply&
}
void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
@ -500,8 +503,7 @@ void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply
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);
SetNpOnlineId(dst.ownerOnlineId, r.owner_npid());
}
dst.owner.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(r.owner_account_id());
dst.owner.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(r.owner_platform());
@ -580,7 +582,7 @@ void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply
void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
@ -607,8 +609,7 @@ void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
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);
SetNpId(npid, r.owner_npid());
dst.ownerNpId = &npid;
}
@ -685,7 +686,7 @@ void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
ReserveExternalRoomPayloadStorage(p, resp);
@ -711,8 +712,7 @@ void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
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);
SetNpOnlineId(dst.ownerOnlineId, r.owner_npid());
}
dst.owner.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(r.owner_account_id());
dst.owner.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(r.owner_platform());
@ -790,7 +790,7 @@ void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
void* BuildGetRoomMemberDataExternalListPayload(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
const int member_count = resp.members_size();
@ -800,7 +800,7 @@ void* BuildGetRoomMemberDataExternalListPayload(
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);
SetNpId(dst.npId, src.npid());
dst.joinDate.tick = src.join_date();
dst.role = static_cast<OrbisNpMatching2Role>(src.role());
}
@ -819,7 +819,7 @@ void* BuildGetRoomMemberDataExternalListPayload(
void* BuildGetRoomMemberDataExternalListPayloadA(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
const int member_count = resp.members_size();
@ -831,7 +831,7 @@ void* BuildGetRoomMemberDataExternalListPayloadA(
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);
SetNpOnlineId(dst.onlineId, src.npid());
dst.joinDate.tick = src.join_date();
dst.role = static_cast<OrbisNpMatching2Role>(src.role());
}
@ -849,7 +849,7 @@ void* BuildGetRoomMemberDataExternalListPayloadA(
}
void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
size_t total_bin_attrs = 0;
@ -866,7 +866,7 @@ void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfo
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);
SetNpId(dst.npId, u.npid());
if (u.user_bin_attrs_size() > 0) {
OrbisNpMatching2BinAttr* first = nullptr;
@ -899,7 +899,7 @@ void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfo
}
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
size_t total_bin_attrs = 0;
@ -916,7 +916,7 @@ void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInf
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);
SetNpOnlineId(dst.userOnlineId, u.npid());
dst.user.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(u.account_id());
dst.user.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(u.platform());
@ -957,7 +957,7 @@ void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId
}
const RoomCache& rc = rc_it->second;
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p = RequestPayload(ctx);
p.Reset();
p.room_groups.clear();
@ -1041,6 +1041,68 @@ void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId
return p.request_data;
}
void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatching2CastType castType,
const std::vector<OrbisNpMatching2RoomMemberId>& dstMembers,
const MemberCache* srcMember, const std::vector<u8>& msg) {
p.Reset();
p.room_message_dst = std::make_unique<OrbisNpMatching2RoomMessageDestination>();
*p.room_message_dst = {};
if (castType == ORBIS_NP_MATCHING2_CASTTYPE_UNICAST && !dstMembers.empty()) {
p.room_message_dst->unicastTarget = dstMembers.front();
} else if (castType == ORBIS_NP_MATCHING2_CASTTYPE_MULTICAST && !dstMembers.empty()) {
p.room_message_multicast_members = dstMembers;
p.room_message_dst->multicastTarget.memberId = p.room_message_multicast_members.data();
p.room_message_dst->multicastTarget.memberIdNum = p.room_message_multicast_members.size();
}
p.room_message_data = msg;
void* msg_ptr = p.room_message_data.empty() ? nullptr : p.room_message_data.data();
if (a_variant) {
p.room_message_src_addr = std::make_unique<Libraries::Np::OrbisNpPeerAddressA>();
p.room_message_src_online_id = std::make_unique<Libraries::Np::OrbisNpOnlineId>();
*p.room_message_src_addr = {};
*p.room_message_src_online_id = {};
if (srcMember) {
p.room_message_src_addr->accountId = srcMember->account_id;
p.room_message_src_addr->platform = srcMember->platform;
*p.room_message_src_online_id = srcMember->np_id.handle;
}
p.room_message_info_a = std::make_unique<OrbisNpMatching2RoomMessageInfoA>();
auto& info = *p.room_message_info_a;
info = {};
info.filtered = false;
info.castType = castType;
info.dst = p.room_message_dst.get();
info.srcMember = p.room_message_src_addr.get();
info.srcOnlineId = p.room_message_src_online_id.get();
info.msg = msg_ptr;
info.msgLen = static_cast<u32>(p.room_message_data.size());
p.room_message_callback_data = p.room_message_info_a.get();
return p.room_message_callback_data;
}
p.room_message_src_npid = std::make_unique<Libraries::Np::OrbisNpId>();
*p.room_message_src_npid = {};
if (srcMember) {
*p.room_message_src_npid = srcMember->np_id;
}
p.room_message_info = std::make_unique<OrbisNpMatching2RoomMessageInfo>();
auto& info = *p.room_message_info;
info = {};
info.filtered = false;
info.castType = castType;
info.dst = p.room_message_dst.get();
info.srcMember = p.room_message_src_npid.get();
info.msg = msg_ptr;
info.msgLen = static_cast<u32>(p.room_message_data.size());
p.room_message_callback_data = p.room_message_info.get();
return p.room_message_callback_data;
}
ContextManager& ContextManager::Instance() {
static ContextManager instance;
return instance;
@ -1106,15 +1168,36 @@ ContextObject* ContextManager::Get(OrbisNpMatching2ContextId ctx_id) {
bool ContextManager::Destroy(OrbisNpMatching2ContextId ctx_id) {
std::lock_guard lock(m_mutex);
if (!GetLocked(ctx_id)) {
ContextObject* ctx = GetLocked(ctx_id);
if (!ctx) {
return false;
}
if (ctx->stop_pending) {
ctx->destroy_pending = true;
LOG_DEBUG(Lib_NpMatching2, "context destroy deferred until stop callback: id={}", ctx_id);
return true;
}
m_contexts[ctx_id].Reset();
m_used[ctx_id] = false;
LOG_DEBUG(Lib_NpMatching2, "context destroyed: id={}", ctx_id);
return true;
}
void ContextManager::CompleteStop(OrbisNpMatching2ContextId ctx_id) {
std::lock_guard lock(m_mutex);
ContextObject* ctx = GetLocked(ctx_id);
if (!ctx) {
return;
}
ctx->stop_pending = false;
if (!ctx->destroy_pending) {
return;
}
m_contexts[ctx_id].Reset();
m_used[ctx_id] = false;
LOG_DEBUG(Lib_NpMatching2, "context destroyed after stop callback: id={}", ctx_id);
}
s32 ContextManager::Start(OrbisNpMatching2ContextId ctx_id) {
std::lock_guard lock(m_mutex);
ContextObject* ctx = GetLocked(ctx_id);
@ -1125,6 +1208,8 @@ s32 ContextManager::Start(OrbisNpMatching2ContextId ctx_id) {
return ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_STARTED;
}
ctx->started = true;
ctx->stop_pending = false;
ctx->destroy_pending = false;
LOG_DEBUG(Lib_NpMatching2, "context started: id={}", ctx_id);
return ORBIS_OK;
}
@ -1139,6 +1224,7 @@ s32 ContextManager::Stop(OrbisNpMatching2ContextId ctx_id) {
return ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED;
}
ctx->started = false;
ctx->stop_pending = true;
LOG_DEBUG(Lib_NpMatching2, "context stopped: id={}", ctx_id);
return ORBIS_OK;
}
@ -1186,12 +1272,29 @@ void SetInitialized(bool initialized) {
}
void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt) {
ctx->per_request_callback = nullptr;
ctx->per_request_callback_arg = nullptr;
if (requestOpt && requestOpt->callback) {
ctx->default_request_callback = requestOpt->callback;
ctx->default_request_callback_arg = requestOpt->arg;
ctx->per_request_callback = requestOpt->callback;
ctx->per_request_callback_arg = requestOpt->arg;
}
}
RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx) {
RequestCallbackInfo cb{};
if (ctx->per_request_callback) {
cb.callback = ctx->per_request_callback;
cb.arg = ctx->per_request_callback_arg;
ctx->per_request_callback = nullptr;
ctx->per_request_callback_arg = nullptr;
return cb;
}
cb.callback = ctx->default_request_callback;
cb.arg = ctx->default_request_callback_arg;
return cb;
}
namespace {
void FireEvent(const PendingEvent& ev) {
@ -1213,6 +1316,9 @@ void FireEvent(const PendingEvent& ev) {
LOG_WARNING(Lib_NpMatching2, "callback CONTEXT ctx={} event={:#x} SKIPPED: no callback",
ev.ctx_id, static_cast<u16>(ev.ctx_event));
}
if (ev.ctx_event == ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED) {
ContextManager::Instance().CompleteStop(ev.ctx_id);
}
break;
case PendingEvent::REQUEST_CB:
if (ev.request_cb) {

View File

@ -132,6 +132,16 @@ struct CallbackPayload {
std::vector<Libraries::Np::OrbisNpId> ext_owner_npids;
void* request_data = nullptr;
std::unique_ptr<OrbisNpMatching2RoomMessageInfo> room_message_info;
std::unique_ptr<OrbisNpMatching2RoomMessageInfoA> room_message_info_a;
std::unique_ptr<OrbisNpMatching2RoomMessageDestination> room_message_dst;
std::unique_ptr<Libraries::Np::OrbisNpId> room_message_src_npid;
std::unique_ptr<Libraries::Np::OrbisNpPeerAddressA> room_message_src_addr;
std::unique_ptr<Libraries::Np::OrbisNpOnlineId> room_message_src_online_id;
std::vector<OrbisNpMatching2RoomMemberId> room_message_multicast_members;
std::vector<u8> room_message_data;
void* room_message_callback_data = nullptr;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdate> room_member_update;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdateA> room_member_update_a;
std::unique_ptr<OrbisNpMatching2RoomUpdate> room_update;
@ -186,6 +196,15 @@ struct CallbackPayload {
ext_room_groups.clear();
ext_owner_npids.clear();
request_data = nullptr;
room_message_info.reset();
room_message_info_a.reset();
room_message_dst.reset();
room_message_src_npid.reset();
room_message_src_addr.reset();
room_message_src_online_id.reset();
room_message_multicast_members.clear();
room_message_data.clear();
room_message_callback_data = nullptr;
room_member_update.reset();
room_member_update_a.reset();
room_update.reset();
@ -203,6 +222,8 @@ struct CallbackPayload {
struct ContextObject {
OrbisNpMatching2ContextId ctx_id = 0;
bool started = false;
bool stop_pending = false;
bool destroy_pending = false;
bool a_variant = false;
OrbisNpMatching2ServerId server_id = 1;
@ -210,9 +231,6 @@ struct ContextObject {
Libraries::Np::OrbisNpId owner_np_id{};
Libraries::Np::OrbisNpOnlineId online_id{};
std::string signaling_addr;
u16 signaling_port = 0;
u64 handler_registration_generation = 1;
OrbisNpMatching2WorldId world_id = 0;
@ -228,6 +246,7 @@ struct ContextObject {
std::map<OrbisNpMatching2RoomId, RoomCache> room_cache;
CallbackPayload request_payload;
CallbackPayload* request_payload_override = nullptr;
CallbackPayload room_event_payload;
OrbisNpMatching2ContextCallback context_callback = nullptr;
@ -250,13 +269,13 @@ struct ContextObject {
void Reset() {
ctx_id = 0;
started = false;
stop_pending = false;
destroy_pending = false;
a_variant = false;
server_id = 1;
service_label = 0;
owner_np_id = {};
online_id = {};
signaling_addr.clear();
signaling_port = 0;
handler_registration_generation = 1;
world_id = 0;
lobby_id = 0;
@ -268,6 +287,7 @@ struct ContextObject {
peers.clear();
room_cache.clear();
request_payload.Reset();
request_payload_override = nullptr;
room_event_payload.Reset();
context_callback = nullptr;
context_callback_arg = nullptr;
@ -288,6 +308,11 @@ struct ContextObject {
}
};
struct RequestCallbackInfo {
OrbisNpMatching2RequestCallback callback = nullptr;
void* arg = nullptr;
};
class ContextManager {
public:
static constexpr u32 kMaxContexts = 10;
@ -300,6 +325,7 @@ public:
bool Check(OrbisNpMatching2ContextId ctx_id);
ContextObject* Get(OrbisNpMatching2ContextId ctx_id);
bool Destroy(OrbisNpMatching2ContextId ctx_id);
void CompleteStop(OrbisNpMatching2ContextId ctx_id);
s32 Start(OrbisNpMatching2ContextId ctx_id);
s32 Stop(OrbisNpMatching2ContextId ctx_id);
@ -344,6 +370,7 @@ struct PendingEvent {
OrbisNpMatching2RequestCallback request_cb = nullptr;
void* request_cb_arg = nullptr;
void* request_data = nullptr;
std::shared_ptr<CallbackPayload> request_payload_owner;
OrbisNpMatching2RoomId room_id = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
@ -382,6 +409,7 @@ OrbisNpMatching2RequestId AllocRequestId();
bool IsInitialized();
void SetInitialized(bool initialized);
void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt);
RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx);
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
@ -400,6 +428,9 @@ void* BuildGetRoomMemberDataExternalListPayloadA(
void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatching2CastType castType,
const std::vector<OrbisNpMatching2RoomMemberId>& dstMembers,
const MemberCache* srcMember, const std::vector<u8>& msg);
void InitEventDispatcher();
void TermEventDispatcher();

View File

@ -3,6 +3,7 @@
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <map>
#include <memory>
@ -12,6 +13,7 @@
#include "common/logging/log.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/net_upnp.h"
#include "core/libraries/network/sockets.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
@ -30,6 +32,8 @@ struct PendingRequest {
OrbisNpMatching2RequestId req_id = 0;
OrbisNpMatching2Event req_event{};
bool a_variant = false;
OrbisNpMatching2RequestCallback request_cb = nullptr;
void* request_cb_arg = nullptr;
};
struct MmClientState {
@ -37,6 +41,7 @@ struct MmClientState {
std::mutex mutex;
u32 server_addr = 0;
u16 server_udp_port = 0;
bool matching2_enabled = false;
std::mutex pending_mutex;
std::map<u64, PendingRequest> pending;
@ -56,6 +61,16 @@ u32 IpStringToAddr(std::string_view ip) {
return a | (b << 8) | (c << 16) | (d << 24);
}
bool IsMatching2BackendDisabled() {
std::lock_guard lock(g_mm.mutex);
return !g_mm.matching2_enabled;
}
bool IsContextLifecycleEvent(OrbisNpMatching2Event event) {
return event == ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED ||
event == ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED;
}
template <typename T>
std::vector<u8> MakeProtoPayload(const T& msg) {
std::string bytes;
@ -160,8 +175,11 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
}
void* request_data = nullptr;
std::shared_ptr<CallbackPayload> request_payload_owner;
if (error_code == 0) {
request_payload_owner = std::make_shared<CallbackPayload>();
ctx->request_payload_override = request_payload_owner.get();
const std::string proto = ExtractProtoBytes(body);
if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A) {
@ -221,6 +239,7 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
: BuildGetUserInfoListPayload(*ctx, reply);
}
}
ctx->request_payload_override = nullptr;
}
PendingEvent ev{};
@ -230,9 +249,10 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
ev.req_id = pr.req_id;
ev.req_event = pr.req_event;
ev.error_code = error_code;
ev.request_cb = ctx->default_request_callback;
ev.request_cb_arg = ctx->default_request_callback_arg;
ev.request_cb = pr.request_cb;
ev.request_cb_arg = pr.request_cb_arg;
ev.request_data = request_data;
ev.request_payload_owner = std::move(request_payload_owner);
ScheduleEvent(std::move(ev));
if (error_code == 0 && (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM ||
@ -284,7 +304,7 @@ void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCach
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.onlineId = mc.np_id.handle;
m.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data();
m.roomMemberInternalBinAttr =
p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data();
@ -331,8 +351,8 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
const auto room_id = static_cast<OrbisNpMatching2RoomId>(n.room_id);
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(n.member_id);
CallbackPayload& p = ctx->room_event_payload;
p.Reset();
auto payload_owner = std::make_shared<CallbackPayload>();
CallbackPayload& p = *payload_owner;
switch (event) {
case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED: {
@ -350,7 +370,7 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
mc.join_date = n.member_join_date;
mc.addr = IpStringToAddr(n.member_addr);
mc.port = Libraries::Net::sceNetHtons(static_cast<u16>(n.member_port));
std::strncpy(mc.np_id.handle.data, n.member_npid.c_str(), sizeof(mc.np_id.handle.data) - 1);
SetNpId(mc.np_id, n.member_npid);
mc.account_id = static_cast<Libraries::Np::OrbisNpAccountId>(n.member_account_id);
mc.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(n.member_platform);
for (const auto& a : n.member_bin_attrs) {
@ -363,7 +383,7 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
pi.member_id = member_id;
pi.addr = mc.addr;
pi.port = mc.port;
std::strncpy(pi.online_id.data, n.member_npid.c_str(), sizeof(pi.online_id.data) - 1);
SetNpOnlineId(pi.online_id, n.member_npid);
ctx->peers[member_id] = pi;
BuildMemberUpdate(p, room_it->second, mc, cause, ctx->a_variant);
@ -545,6 +565,7 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
ev.room_id = room_id;
ev.room_event = fired_event;
ev.room_event_data = p.room_event_data;
ev.request_payload_owner = std::move(payload_owner);
ScheduleEvent(std::move(ev));
if (event == ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED) {
@ -552,6 +573,57 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
}
}
void HandleRoomMessage(const ShadNet::NotifyRoomMessage& n) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(n.ctx_id));
if (!ctx) {
return;
}
const auto room_id = static_cast<OrbisNpMatching2RoomId>(n.room_id);
const auto src_member_id = static_cast<OrbisNpMatching2RoomMemberId>(n.src_member_id);
const MemberCache* src_member = nullptr;
const auto room_it = ctx->room_cache.find(room_id);
if (room_it != ctx->room_cache.end()) {
const auto member_it = room_it->second.members.find(src_member_id);
if (member_it != room_it->second.members.end()) {
src_member = &member_it->second;
}
}
MemberCache fallback_src{};
if (!src_member) {
fallback_src.member_id = src_member_id;
SetNpId(fallback_src.np_id, n.src_npid);
fallback_src.account_id = static_cast<Libraries::Np::OrbisNpAccountId>(n.src_account_id);
fallback_src.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(n.src_platform);
src_member = &fallback_src;
}
std::vector<OrbisNpMatching2RoomMemberId> dst_members;
dst_members.reserve(n.dst_member_ids.size());
for (const u32 member_id : n.dst_member_ids) {
dst_members.push_back(static_cast<OrbisNpMatching2RoomMemberId>(member_id));
}
auto payload_owner = std::make_shared<CallbackPayload>();
void* data = BuildRoomMessagePayload(*payload_owner, ctx->a_variant,
static_cast<OrbisNpMatching2CastType>(n.cast_type),
dst_members, src_member, n.msg);
PendingEvent ev{};
ev.type = PendingEvent::ROOM_MESSAGE_CB;
ev.ctx_id = ctx->ctx_id;
ev.fire_at = std::chrono::steady_clock::now();
ev.room_id = room_id;
ev.src_member_id = src_member_id;
ev.msg_event = ctx->a_variant ? ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE_A
: ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE;
ev.message_data = data;
ev.request_payload_owner = std::move(payload_owner);
ScheduleEvent(std::move(ev));
}
} // namespace
void OnMatchingReply(ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error,
@ -584,21 +656,28 @@ void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> client,
std::string_view server_host, u16 tcp_port) {
u32 server_addr = 0;
u16 server_udp_port = 0;
bool matching2_enabled = false;
{
std::lock_guard lock(g_mm.mutex);
g_mm.client = client;
g_mm.matching2_enabled = client ? client->IsMatching2Enabled() : false;
matching2_enabled = g_mm.matching2_enabled;
g_mm.server_addr = client ? client->GetAddrServer() : 0;
if (g_mm.server_addr == 0) {
g_mm.server_addr = IpStringToAddr(server_host);
}
g_mm.server_udp_port = Libraries::Net::sceNetHtons(static_cast<u16>(tcp_port + 1));
g_mm.server_udp_port =
matching2_enabled ? Libraries::Net::sceNetHtons(static_cast<u16>(tcp_port + 1)) : 0;
server_addr = g_mm.server_addr;
server_udp_port = g_mm.server_udp_port;
}
LOG_INFO(Lib_NpMatching2, "ShadNet features: matching2_enabled={}", matching2_enabled);
Net::UPnPClient::Instance().SetP2PFeaturesEnabled(matching2_enabled);
if (!client) {
NpSignaling::Stubs::SetTransportHooks({});
NpSignaling::Stubs::SetPeerResolver(nullptr);
NpSignaling::Stubs::SetMatching2Enabled(false);
NpSignaling::Stubs::SetMmServerEndpoint(0, 0);
StopMatching2HandshakeThread();
return;
@ -614,10 +693,16 @@ void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> client,
.advertised_addr = Net::GetP2PAdvertisedAddr,
.ensure_transport = Net::EnsureP2PTransport,
});
NpSignaling::Stubs::SetPeerResolver(RequestSignalingInfos);
NpSignaling::Stubs::SetPeerResolver(matching2_enabled ? RequestSignalingInfos : nullptr);
NpSignaling::Stubs::SetMatching2Enabled(matching2_enabled);
NpSignaling::Stubs::SetMmServerEndpoint(server_addr, server_udp_port);
StartMatching2HandshakeThread();
if (matching2_enabled) {
StartMatching2HandshakeThread();
} else {
StopMatching2HandshakeThread();
}
client->onRoomEvent = [](const ShadNet::NotifyRoomEvent& n) { HandleRoomEvent(n); };
client->onRoomMessage = [](const ShadNet::NotifyRoomMessage& n) { HandleRoomMessage(n); };
}
void ClearMmShadNetClient() {
@ -627,12 +712,16 @@ void ClearMmShadNetClient() {
old_client = std::move(g_mm.client);
g_mm.server_addr = 0;
g_mm.server_udp_port = 0;
g_mm.matching2_enabled = false;
}
Net::UPnPClient::Instance().SetP2PFeaturesEnabled(false);
if (old_client) {
old_client->onRoomEvent = nullptr;
old_client->onRoomMessage = nullptr;
}
NpSignaling::Stubs::SetTransportHooks({});
NpSignaling::Stubs::SetPeerResolver(nullptr);
NpSignaling::Stubs::SetMatching2Enabled(false);
NpSignaling::Stubs::SetMmServerEndpoint(0, 0);
StopMatching2HandshakeThread();
{
@ -657,11 +746,11 @@ void MmContextStart(OrbisNpMatching2ContextId ctx_id) {
}
}
void MmContextStop(OrbisNpMatching2ContextId ctx_id) {
s32 MmContextStop(OrbisNpMatching2ContextId ctx_id) {
shadnet::ContextStopRequest req;
req.set_ctx_id(ctx_id);
MmSubmitRequest(ctx_id, 0, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED, MmCommand::ContextStop,
MakeProtoPayload(req));
return MmSubmitRequest(ctx_id, 0, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED,
MmCommand::ContextStop, MakeProtoPayload(req));
}
s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
@ -677,10 +766,66 @@ s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
ContextObject* ctx = ContextManager::Instance().Get(ctx_id);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "MmSubmitRequest({}): invalid ctx={}", static_cast<u16>(cmd),
ctx_id);
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
const RequestCallbackInfo request_cb = ConsumeRequestCallback(ctx);
if (IsMatching2BackendDisabled() && !IsContextLifecycleEvent(req_event)) {
LOG_INFO(Lib_NpMatching2,
"MmSubmitRequest: matching2 backend disabled; failing ctx={} reqId={} "
"event={:#x} cmd={}",
ctx_id, req_id, static_cast<u16>(req_event), static_cast<u16>(cmd));
s32 error_code = ORBIS_NP_MATCHING2_ERROR_SERVER_NOT_AVAILABLE;
void* request_data = nullptr;
std::shared_ptr<CallbackPayload> request_payload_owner;
if (req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST) {
LOG_INFO(Lib_NpMatching2,
"MmSubmitRequest: matching2 backend disabled; returning empty world");
shadnet::GetWorldInfoListReply reply;
auto* world = reply.add_worlds();
world->set_world_id(1);
world->set_lobbies_num(0);
world->set_max_lobby_members(0);
world->set_lobby_members_num(0);
world->set_rooms_num(0);
world->set_room_members_num(0);
request_payload_owner = std::make_shared<CallbackPayload>();
ctx->request_payload_override = request_payload_owner.get();
request_data = BuildGetWorldInfoListPayload(*ctx, reply);
ctx->request_payload_override = nullptr;
error_code = ORBIS_OK;
}
PendingEvent ev{};
ev.type = PendingEvent::REQUEST_CB;
ev.ctx_id = ctx_id;
ev.fire_at = std::chrono::steady_clock::now();
ev.req_id = req_id;
ev.req_event = req_event;
ev.error_code = error_code;
ev.request_cb = request_cb.callback;
ev.request_cb_arg = request_cb.arg;
ev.request_data = request_data;
ev.request_payload_owner = std::move(request_payload_owner);
ScheduleEvent(std::move(ev));
return ORBIS_OK;
}
LOG_INFO(Lib_NpMatching2,
"MmSubmitRequest: ctx={} reqId={} event={:#x} cmd={} aVariant={} callback={:#x} "
"arg={}",
ctx_id, req_id, static_cast<u16>(req_event), static_cast<u16>(cmd), a_variant,
reinterpret_cast<std::uintptr_t>(request_cb.callback), fmt::ptr(request_cb.arg));
const u64 pkt_id = client->SubmitRequest(static_cast<ShadNet::CommandType>(cmd), payload);
{
std::lock_guard lock(g_mm.pending_mutex);
g_mm.pending[pkt_id] = {ctx_id, req_id, req_event, a_variant};
g_mm.pending[pkt_id] = {ctx_id, req_id, req_event, a_variant, request_cb.callback,
request_cb.arg};
}
LOG_DEBUG(Lib_NpMatching2, "submit cmd={} pkt_id={} ctx={} reqId={}", static_cast<u16>(cmd),
pkt_id, ctx_id, req_id);
@ -956,6 +1101,30 @@ s32 MmSetUserInfo(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId re
MmCommand::SetUserInfo, MakeProtoPayload(req));
}
s32 MmSendRoomMessage(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SendRoomMessageRequest& request) {
shadnet::SendRoomMessageRequest req;
req.set_room_id(request.roomId);
req.set_cast_type(request.castType);
req.set_option(request.option);
if (request.castType == ORBIS_NP_MATCHING2_CASTTYPE_UNICAST) {
req.add_dst_member_ids(request.dst.unicastTarget);
} else if (request.castType == ORBIS_NP_MATCHING2_CASTTYPE_MULTICAST &&
request.dst.multicastTarget.memberId) {
for (u64 i = 0; i < request.dst.multicastTarget.memberIdNum; ++i) {
req.add_dst_member_ids(request.dst.multicastTarget.memberId[i]);
}
}
if (request.msg && request.msgLen > 0) {
req.set_msg(request.msg, request.msgLen);
}
return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE,
MmCommand::SendRoomMessage, MakeProtoPayload(req));
}
s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataInternalRequest& request) {
shadnet::SetRoomDataInternalRequest req;
@ -1017,6 +1186,9 @@ bool RequestSignalingInfos(std::string_view target_online_id, u32* out_addr, u16
if (!out_addr || !out_port) {
return false;
}
if (IsMatching2BackendDisabled()) {
return false;
}
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(g_mm.mutex);

View File

@ -19,21 +19,22 @@ enum class ErrorType : u8;
namespace Libraries::Np::NpMatching2 {
enum class MmCommand : u16 {
ContextStart = 12,
CreateRoom = 13,
JoinRoom = 14,
LeaveRoom = 15,
SearchRoom = 16,
RequestSignalingInfos = 17,
ContextStop = 18,
SetUserInfo = 19,
SetRoomDataInternal = 20,
SetRoomDataExternal = 21,
KickoutRoomMember = 22,
GetWorldInfoList = 23,
GetRoomDataExternalList = 24,
GetUserInfoList = 25,
GetRoomMemberDataExternalList = 26,
ContextStart = 100,
CreateRoom = 101,
JoinRoom = 102,
LeaveRoom = 103,
SearchRoom = 104,
RequestSignalingInfos = 105,
ContextStop = 106,
SetUserInfo = 107,
SetRoomDataInternal = 108,
SetRoomDataExternal = 109,
KickoutRoomMember = 110,
GetWorldInfoList = 111,
GetRoomDataExternalList = 112,
GetUserInfoList = 113,
GetRoomMemberDataExternalList = 114,
SendRoomMessage = 115,
};
void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> client,
@ -45,7 +46,7 @@ void OnMatchingReply(ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType er
const std::vector<u8>& body);
void MmContextStart(OrbisNpMatching2ContextId ctx_id);
void MmContextStop(OrbisNpMatching2ContextId ctx_id);
s32 MmContextStop(OrbisNpMatching2ContextId ctx_id);
s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector<u8>& payload,
@ -76,6 +77,8 @@ s32 MmGetUserInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestI
bool a_variant = false);
s32 MmSetUserInfo(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetUserInfoRequest& request);
s32 MmSendRoomMessage(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SendRoomMessageRequest& request);
s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataInternalRequest& request);
s32 MmSetRoomDataExternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,

View File

@ -9,6 +9,7 @@
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include "common/logging/log.h"
@ -102,8 +103,7 @@ bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
peer.addr = member.addr;
peer.port = member.port;
if (peer.online_id.data[0] == 0) {
std::strncpy(peer.online_id.data, member.np_id.handle.data,
sizeof(peer.online_id.data) - 1);
peer.online_id = member.np_id.handle;
}
return true;
}
@ -116,8 +116,7 @@ bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
peer.port = resolved_port;
}
if (peer.online_id.data[0] == 0) {
std::strncpy(peer.online_id.data, member.np_id.handle.data,
sizeof(peer.online_id.data) - 1);
peer.online_id = member.np_id.handle;
}
return peer.addr != 0 && peer.port != 0;
}
@ -238,7 +237,9 @@ void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port,
peer.status =
peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending;
peer.handshake_started = true;
std::memcpy(peer.online_id.data, pkt.online_id_from, ORBIS_NP_ONLINEID_MAX_LENGTH);
SetNpOnlineId(peer.online_id,
std::string_view(reinterpret_cast<const char*>(pkt.online_id_from),
ORBIS_NP_ONLINEID_MAX_LENGTH));
const auto kind = static_cast<Matching2HandshakeKind>(pkt.kind);
switch (kind) {
@ -310,7 +311,7 @@ void Matching2HandshakeThreadMain() {
now - peer.last_send > kMatching2HandshakeTimeout) {
peer.status = kMatching2ConnInactive;
QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_NETINFO_ERROR,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT);
continue;
}
@ -334,6 +335,9 @@ void Matching2HandshakeThreadMain() {
} // namespace
bool SendMatching2StunPing(const ContextObject& ctx) {
if (!NpSignaling::Stubs::Matching2Enabled()) {
return false;
}
if (!ctx.started || ctx.online_id.data[0] == '\0') {
return false;
}
@ -485,7 +489,8 @@ u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
}
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
CallbackPayload& p = ctx.request_payload;
CallbackPayload& p =
ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload;
p.Reset();
const auto room_it = ctx.room_cache.find(roomId);

View File

@ -390,19 +390,21 @@ static void PingThreadMain() {
}
}
const u32 server_addr = Stubs::MmServerAddr();
const u16 server_port = Stubs::MmServerUdpPort();
if (Stubs::Matching2Enabled()) {
const u32 server_addr = Stubs::MmServerAddr();
const u16 server_port = Stubs::MmServerUdpPort();
for (const auto& cs : contexts) {
if (server_addr == 0 || server_port == 0) {
break;
for (const auto& cs : contexts) {
if (server_addr == 0 || server_port == 0) {
break;
}
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);
}
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);
}
SendEchoPings();

View File

@ -6,6 +6,7 @@
#include <cstring>
#include <mutex>
#include <random>
#include <string_view>
#include "common/logging/log.h"
#include "common/singleton.h"
@ -1228,7 +1229,9 @@ void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& p
ci.conn_id = conn_id;
ci.ctx_id = ctx_id;
ci.locally_activated = false;
std::memcpy(ci.online_id.data, pkt.online_id_from, ORBIS_NP_ONLINEID_MAX_LENGTH);
SetNpOnlineId(ci.online_id,
std::string_view(reinterpret_cast<const char*>(pkt.online_id_from),
ORBIS_NP_ONLINEID_MAX_LENGTH));
ci.npid.handle = ci.online_id;
g_connections[conn_id] = std::move(ci);
g_npid_to_conn[MakeCtxNpIdKey(ctx_id, g_connections[conn_id].npid)] = conn_id;
@ -1301,6 +1304,10 @@ void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& p
}
void SendStunPing(s32 ctx_id) {
if (!Stubs::Matching2Enabled()) {
return;
}
OrbisNpOnlineId online_id{};
{

View File

@ -8,6 +8,7 @@ namespace Libraries::Np::NpSignaling::Stubs {
namespace {
TransportHooks g_transport{};
PeerResolver g_peer_resolver = nullptr;
bool g_matching2_enabled = false;
u32 g_mm_server_addr = 0;
u16 g_mm_server_udp_port = 0;
} // namespace
@ -55,6 +56,10 @@ void SetPeerResolver(PeerResolver fn) {
g_peer_resolver = fn;
}
void SetMatching2Enabled(bool enabled) {
g_matching2_enabled = enabled;
}
void SetMmServerEndpoint(u32 addr, u16 udp_port) {
g_mm_server_addr = addr;
g_mm_server_udp_port = udp_port;
@ -64,6 +69,10 @@ bool ResolvePeer(std::string_view online_id, u32* out_addr, u16* out_port) {
return g_peer_resolver ? g_peer_resolver(online_id, out_addr, out_port) : false;
}
bool Matching2Enabled() {
return g_matching2_enabled;
}
u32 MmServerAddr() {
return g_mm_server_addr;
}

View File

@ -31,9 +31,11 @@ bool EnsureTransport();
using PeerResolver = bool (*)(std::string_view online_id, u32* out_addr, u16* out_port);
void SetPeerResolver(PeerResolver fn);
void SetMatching2Enabled(bool enabled);
void SetMmServerEndpoint(u32 addr, u16 udp_port);
bool ResolvePeer(std::string_view online_id, u32* out_addr, u16* out_port);
bool Matching2Enabled();
u32 MmServerAddr();
u16 MmServerUdpPort();

View File

@ -3,6 +3,10 @@
#pragma once
#include <algorithm>
#include <cstring>
#include <string_view>
#include "common/types.h"
#include "core/libraries/error_codes.h"
@ -34,6 +38,20 @@ struct OrbisNpId {
u8 reserved[8];
};
inline void SetNpOnlineId(OrbisNpOnlineId& dst, std::string_view src) {
dst = {};
const size_t len = std::min(src.size(), static_cast<size_t>(ORBIS_NP_ONLINEID_MAX_LENGTH));
if (len > 0) {
std::memcpy(dst.data, src.data(), len);
}
dst.term = 0;
}
inline void SetNpId(OrbisNpId& dst, std::string_view online_id) {
dst = {};
SetNpOnlineId(dst.handle, online_id);
}
struct OrbisNpClientId {
char id[129];
u8 padding[7];
@ -71,4 +89,4 @@ struct OrbisNpPeerAddressA {
char padding[4];
};
}; // namespace Libraries::Np
}; // namespace Libraries::Np

View File

@ -42,6 +42,14 @@ using OrbisNpMatching2SignalingRequestId = u32;
using OrbisNpMatching2TeamId = u8;
using OrbisNpMatching2WorldId = u32;
using OrbisNpMatching2CastType = u8;
enum : OrbisNpMatching2CastType {
ORBIS_NP_MATCHING2_CASTTYPE_BROADCAST = 1,
ORBIS_NP_MATCHING2_CASTTYPE_UNICAST = 2,
ORBIS_NP_MATCHING2_CASTTYPE_MULTICAST = 3,
};
// Event of request/room/signaling/context functions
enum OrbisNpMatching2Event : u16 {
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST = 0x0002,
@ -83,6 +91,7 @@ enum OrbisNpMatching2Event : u16 {
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 = 0x2102,
ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE_A = 0x9102,
};
@ -308,6 +317,48 @@ struct OrbisNpMatching2RoomGroupInfo {
u32 groupMembers;
};
union OrbisNpMatching2RoomMessageDestination {
OrbisNpMatching2RoomMemberId unicastTarget;
struct {
OrbisNpMatching2RoomMemberId* memberId;
u64 memberIdNum;
} multicastTarget;
};
static_assert(sizeof(OrbisNpMatching2RoomMessageDestination) == 0x10);
struct OrbisNpMatching2SendRoomMessageRequest {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2CastType castType;
u8 padding[3];
OrbisNpMatching2RoomMessageDestination dst;
const void* msg;
u32 msgLen;
s32 option;
};
static_assert(sizeof(OrbisNpMatching2SendRoomMessageRequest) == 0x30);
struct OrbisNpMatching2RoomMessageInfo {
bool filtered;
OrbisNpMatching2CastType castType;
u8 padding[2];
OrbisNpMatching2RoomMessageDestination* dst;
Libraries::Np::OrbisNpId* srcMember;
void* msg;
u32 msgLen;
};
struct OrbisNpMatching2RoomMessageInfoA {
bool filtered;
OrbisNpMatching2CastType castType;
u8 padding[2];
OrbisNpMatching2RoomMessageDestination* dst;
Libraries::Np::OrbisNpPeerAddressA* srcMember;
Libraries::Np::OrbisNpOnlineId* srcOnlineId;
void* msg;
u32 msgLen;
};
// Room-internal binary attribute
struct OrbisNpMatching2RoomBinAttrInternal {
Libraries::Rtc::OrbisRtcTick lastUpdate;
@ -781,6 +832,7 @@ struct OrbisNpMatching2JoinedSessionInfo {
};
// User information
// from PS3 this was NpUserInfo2; setting addresses like our other A and nonA types TODO: confirm
struct OrbisNpMatching2UserInfo {
OrbisNpMatching2UserInfo* next;
Libraries::Np::OrbisNpId npId;
@ -791,6 +843,7 @@ struct OrbisNpMatching2UserInfo {
};
// User information (account-id variant)
// from PS3 this was NpUserInfo2; setting addresses like our other A and nonA types TODO: confirm
struct OrbisNpMatching2UserInfoA {
OrbisNpMatching2UserInfoA* next;
Libraries::Np::OrbisNpPeerAddressA user;

View File

@ -1,331 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_web_api2.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpWebApi2 {
s32 PS4_SYSV_ABI sceNpWebApi2AbortRequest(s64 request_id) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2AddHttpRequestHeader() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2AddMultipartPart() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2AddWebTraceTag() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
void PS4_SYSV_ABI sceNpWebApi2CheckTimeout() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateMultipartRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateUserContext(s32 lib_ctx_id,
UserService::OrbisUserServiceUserId user_id) {
if (lib_ctx_id >= 0x8000) {
return ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID;
}
if (user_id == UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}, user_id = {:#x}", lib_ctx_id,
user_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2DeleteRequest(s64 request_id) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2DeleteUserContext(s32 user_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, user_ctx_id = {:#x}", user_ctx_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValue() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValueLength() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2GetMemoryPoolStats(s32 lib_ctx_id,
OrbisNpWebApi2MemoryPoolStats* stats) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2Initialize(s32 lib_http_ctx_id, u64 pool_size) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}",
lib_http_ctx_id, pool_size);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2InitializeForPresence(s32 lib_http_ctx_id, u64 pool_size) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}",
lib_http_ctx_id, pool_size);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntCreateRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize(const OrbisNpWebApi2IntInitializeArgs* args) {
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitializeArgs)) {
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_ERROR(Lib_NpWebApi2,
"(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}'",
args->lib_http_ctx_id, args->pool_size, args->name);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Args* args) {
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) {
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_ERROR(Lib_NpWebApi2,
"(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}', "
"group = {:#x}",
args->lib_http_ctx_id, args->pool_size, args->name, args->push_config_group);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntPushEventCreateCtxIndFilter() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventAbortHandle() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateFilter() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateHandle() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreatePushContext() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteFilter() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteHandle() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeletePushContext() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterCallback() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterPushContextCallback() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventSetHandleTimeout() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventStartPushContextCallback() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterCallback() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterPushContextCallback() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) {
if (data == nullptr || size == 0) {
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, size = {:#x}", request_id,
size);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SendMultipartRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SendRequest() {
if (!EmulatorSettings.IsShadNetEnabled()) {
LOG_INFO(Lib_NpWebApi2, "called, returning shadNet signed out.");
return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN;
}
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SetMultipartContentType() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SetRequestTimeout(s64 request_id, u32 timeout) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, timeout = {}", request_id,
timeout);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2Terminate(s32 lib_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_A9A31C5F6FBA6620() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_03D22863300D2B73() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_97296F7578AAD541() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_E0DF39A36F087DB9() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("zpiPsH7dbFQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2AbortRequest);
LIB_FUNCTION("egOOvrnF6mI", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddHttpRequestHeader);
LIB_FUNCTION("Io7kh1LHDoM", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddMultipartPart);
LIB_FUNCTION("MgsTa76wlEk", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddWebTraceTag);
LIB_FUNCTION("3Tt9zL3tkoc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CheckTimeout);
LIB_FUNCTION("+nz1Vq-NrDA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2CreateMultipartRequest);
LIB_FUNCTION("3EI-OSJ65Xc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CreateRequest);
LIB_FUNCTION("sk54bi6FtYM", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2CreateUserContext);
LIB_FUNCTION("vvzWO-DvG1s", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2DeleteRequest);
LIB_FUNCTION("9X9+cneTGUU", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2DeleteUserContext);
LIB_FUNCTION("hksbskNToEA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetHttpResponseHeaderValue);
LIB_FUNCTION("HwP3aM+c85c", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetHttpResponseHeaderValueLength);
LIB_FUNCTION("Xweb+naPZ8Y", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetMemoryPoolStats);
LIB_FUNCTION("+o9816YQhqQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Initialize);
LIB_FUNCTION("dowMWFgowXY", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2InitializeForPresence);
LIB_FUNCTION("qmINYLuqzaA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntCreateRequest);
LIB_FUNCTION("zXaFo7euxsQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2IntInitialize);
LIB_FUNCTION("9KSGFMRnp3k", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntInitialize2);
LIB_FUNCTION("2hlBNB96saE", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntPushEventCreateCtxIndFilter);
LIB_FUNCTION("1OLgvahaSco", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventAbortHandle);
LIB_FUNCTION("MsaFhR+lPE4", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreateFilter);
LIB_FUNCTION("WV1GwM32NgY", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreateHandle);
LIB_FUNCTION("NNVf18SlbT8", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreatePushContext);
LIB_FUNCTION("KJdPcOGmK58", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeleteFilter);
LIB_FUNCTION("fIATVMo4Y1w", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeleteHandle);
LIB_FUNCTION("QafxeZM3WK4", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeletePushContext);
LIB_FUNCTION("fY3QqeNkF8k", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventRegisterCallback);
LIB_FUNCTION("lxtHJMwBsaU", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventRegisterPushContextCallback);
LIB_FUNCTION("KWkc6Q3tjXc", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventSetHandleTimeout);
LIB_FUNCTION("AAj9X+4aGYA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventStartPushContextCallback);
LIB_FUNCTION("hOnIlcGrO6g", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventUnregisterCallback);
LIB_FUNCTION("PmyrbbJSFz0", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventUnregisterPushContextCallback);
LIB_FUNCTION("OOY9+ObfKec", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2ReadData);
LIB_FUNCTION("NKCwS8+5Fx8", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SendMultipartRequest);
LIB_FUNCTION("lQOCF84lvzw", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2SendRequest);
LIB_FUNCTION("bltDCAskmfE", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SetMultipartContentType);
LIB_FUNCTION("TjAutbrkr60", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SetRequestTimeout);
LIB_FUNCTION("bEvXpcEk200", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Terminate);
LIB_FUNCTION("qaMcX2+6ZiA", "libSceNpWebApi2", 1, "libSceNpWebApi2", Func_A9A31C5F6FBA6620);
LIB_FUNCTION("A9IoYzANK3M", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_03D22863300D2B73);
LIB_FUNCTION("lylvdXiq1UE", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_97296F7578AAD541);
LIB_FUNCTION("4N85o28Ifbk", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_E0DF39A36F087DB9);
};
} // namespace Libraries::Np::NpWebApi2

View File

@ -1,40 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpWebApi2 {
struct OrbisNpWebApi2IntInitializeArgs {
s32 lib_http_ctx_id;
s32 reserved;
u64 pool_size;
char* name;
u64 struct_size;
};
struct OrbisNpWebApi2IntInitialize2Args {
s32 lib_http_ctx_id;
s32 reserved;
u64 pool_size;
char* name;
u32 push_config_group;
s32 reserved2;
u64 struct_size;
};
struct OrbisNpWebApi2MemoryPoolStats {
u64 pool_size;
u64 max_inuse_size;
u64 current_inuse_size;
s32 reserved;
};
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,522 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/np/np_web_api2/np_web_api2_internal.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpWebApi2 {
s32 PS4_SYSV_ABI sceNpWebApi2AbortRequest(s64 request_id) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}", request_id);
return abortRequest(request_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2AddHttpRequestHeader(s64 request_id, const char* field_name,
const char* field_value) {
if (!field_name || !field_value) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, field_name = {}, field_value = {}",
request_id, field_name, field_value);
return addHttpRequestHeader(request_id, field_name, field_value);
}
s32 PS4_SYSV_ABI sceNpWebApi2AddMultipartPart() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2AddWebTraceTag(s64 request_id, const char* value) {
if (!value) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, value = {}", request_id, value);
return addHttpRequestHeader(request_id, "X-Psn-WebTrace-Tag", value);
}
void PS4_SYSV_ABI sceNpWebApi2CheckTimeout() {
LOG_TRACE(Lib_NpWebApi2, "called");
return checkTimeout();
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateMultipartRequest(s32 user_ctx_id, const char* api_group,
const char* path, const char* method,
s64* request_id) {
if (!api_group || !path || !method) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, api_group = {}, path = {}, method = {}",
user_ctx_id, api_group, path, method);
s64 temp_request_id = 0;
s32 result =
createRequest(user_ctx_id, api_group, path, method, nullptr, true, &temp_request_id);
if (result == 0) {
LOG_INFO(Lib_NpWebApi2, "created request_id = {:#x}", temp_request_id);
if (request_id) {
*request_id = temp_request_id;
}
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateRequest(s32 user_ctx_id, const char* api_group, const char* path,
const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter,
s64* request_id) {
if (!api_group || !path || !method) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
if (content_parameter && content_parameter->content_length != 0 &&
!content_parameter->content_type) {
LOG_ERROR(Lib_NpWebApi2, "Invalid content parameter");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER;
}
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, api_group = {}, path = {}, method = {}",
user_ctx_id, api_group, path, method);
s64 temp_request_id = 0;
s32 result = createRequest(user_ctx_id, api_group, path, method, content_parameter, false,
&temp_request_id);
if (result == 0) {
LOG_INFO(Lib_NpWebApi2, "created request_id = {:#x}", temp_request_id);
if (request_id) {
*request_id = temp_request_id;
}
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateUserContext(s32 lib_ctx_id,
UserService::OrbisUserServiceUserId user_id) {
if (lib_ctx_id >= 0x8000) {
LOG_ERROR(Lib_NpWebApi2, "Invalid library context id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID;
}
if (user_id == UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
LOG_ERROR(Lib_NpWebApi2, "Invalid user id");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, user_id = {}", lib_ctx_id, user_id);
s32 user_ctx_id = createUserContext(lib_ctx_id, user_id);
if (user_ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created user_ctx_id = {:#x}", user_ctx_id);
}
return user_ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2DeleteRequest(s64 request_id) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}", request_id);
return deleteRequest(request_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2DeleteUserContext(s32 user_ctx_id) {
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}", user_ctx_id);
return deleteUserContext(user_ctx_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValue(s64 request_id, const char* field_name,
u64* field_value_length) {
if (!field_name || !field_value_length) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s32 result = getHttpResponseHeaderData(request_id, field_name, nullptr, 0, field_value_length);
if (result >= 0) {
LOG_INFO(Lib_NpWebApi2,
"on request_id = {:#x}, field_name = {} returned field_value_length = {:#x}",
request_id, field_name, *field_value_length);
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValueLength(s64 request_id,
const char* field_name, char* value,
u64 value_size) {
if (!field_name || !value || value_size == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, field_name = {}", request_id, field_name);
return getHttpResponseHeaderData(request_id, field_name, value, value_size, nullptr);
}
s32 PS4_SYSV_ABI sceNpWebApi2GetMemoryPoolStats(s32 lib_ctx_id,
OrbisNpWebApi2MemoryPoolStats* stats) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}", lib_ctx_id);
return getMemoryPoolStats(lib_ctx_id, stats);
}
s32 PS4_SYSV_ABI sceNpWebApi2Initialize(s32 lib_http_ctx_id, u64 pool_size) {
LOG_DEBUG(Lib_NpWebApi2, "called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", lib_http_ctx_id,
pool_size);
// Uses a sceLncUtilGetAppStatus check to enable debug mode. For now, default to normal.
s32 ctx_id = createLibraryContext(lib_http_ctx_id, 1, pool_size, nullptr);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2InitializeForPresence(s32 lib_http_ctx_id, u64 pool_size) {
LOG_DEBUG(Lib_NpWebApi2, "called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", lib_http_ctx_id,
pool_size);
s32 ctx_id = createLibraryContext(lib_http_ctx_id, 3, pool_size, nullptr);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntCreateRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize(const OrbisNpWebApi2IntInitializeArgs* args) {
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitializeArgs)) {
LOG_ERROR(Lib_NpWebApi2, "Invalid arguments");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2, "called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = {}",
args->lib_http_ctx_id, args->pool_size, args->name ? args->name : "(null)");
s32 ctx_id = createLibraryContext(args->lib_http_ctx_id, 2, args->pool_size, args->name);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Args* args) {
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) {
LOG_ERROR(Lib_NpWebApi2, "Invalid arguments");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(
Lib_NpWebApi2,
"called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = {}, push_config_group = {:#x}",
args->lib_http_ctx_id, args->pool_size, args->name ? args->name : "(null)",
args->push_config_group);
s32 ctx_id = createLibraryContext(args->lib_http_ctx_id, 2, args->pool_size, args->name);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntPushEventCreateCtxIndFilter() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventAbortHandle(s32 lib_ctx_id, s32 handle_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, handle_id = {:#x}", lib_ctx_id, handle_id);
return abortPushEventHandle(lib_ctx_id, handle_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateFilter(
s32 lib_ctx_id, s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param, u64 filter_param_num) {
if ((np_service_name && np_service_label == ORBIS_NP_INVALID_SERVICE_LABEL) || !filter_param ||
filter_param_num == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2,
"called, lib_ctx_id = {:#x}, handle_id = {:#x}, np_service_name = {}, "
"np_service_label = {:#x}, filter_param_num = {:#x}",
lib_ctx_id, handle_id, np_service_name ? np_service_name : "(null)",
static_cast<u32>(np_service_label), filter_param_num);
for (u64 i = 0; i < filter_param_num; i++) {
LOG_DEBUG(Lib_NpWebApi2, "filter_param[{}].data_type.val = {}", i,
filter_param[i].data_type.val);
LOG_DEBUG(Lib_NpWebApi2, "filter_param[{}].extd_data_key_num = {}", i,
filter_param[i].extd_data_key_num);
for (u64 j = 0; j < filter_param[i].extd_data_key_num && filter_param[i].extd_data_key;
j++) {
LOG_DEBUG(Lib_NpWebApi2, "filter_param[{}].extd_data_key[{}].val = {}", i, j,
filter_param[i].extd_data_key[j].val);
}
}
s32 filter_id = createPushEventFilter(lib_ctx_id, handle_id, np_service_name, np_service_label,
filter_param, filter_param_num, false);
if (filter_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created filter_id = {:#x}", filter_id);
}
return filter_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateHandle(s32 lib_ctx_id) {
LOG_DEBUG(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}", lib_ctx_id);
s32 handle_id = createPushEventHandle(lib_ctx_id);
if (handle_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created handle_id = {:#x}", handle_id);
}
return handle_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreatePushContext(
s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
if (!push_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2, "called, user_ctx_id = {:#x}", user_ctx_id);
s32 result = createPushContext(user_ctx_id, push_ctx_id);
if (result == 0) {
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_INFO(Lib_NpWebApi2, "created push_ctx_id = {:#x}", raw_id);
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteFilter(s32 lib_ctx_id, s32 filter_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, filter_id = {:#x}", lib_ctx_id, filter_id);
return deletePushEventFilter(lib_ctx_id, filter_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteHandle(s32 lib_ctx_id, s32 handle_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, handle_id = {:#x}", lib_ctx_id, handle_id);
return deletePushEventHandle(lib_ctx_id, handle_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeletePushContext(
s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
if (!push_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, push_ctx_id = {:#x}", user_ctx_id,
raw_id);
return deletePushContext(user_ctx_id, push_ctx_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventCallback cb_func,
void* user_arg) {
LOG_DEBUG(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, filter_id = {:#x}", user_ctx_id,
filter_id);
if (!cb_func) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s32 callback_id = registerPushEventCallback(user_ctx_id, filter_id, cb_func, user_arg);
if (callback_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created callback_id = {:#x}", callback_id);
}
return callback_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterPushContextCallback(
s32 user_ctx_id, s32 filter_id, OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg) {
LOG_DEBUG(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, filter_id = {:#x}", user_ctx_id,
filter_id);
if (!cb_func) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s32 callback_id = registerPushContextCallback(user_ctx_id, filter_id, cb_func, user_arg);
if (callback_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created callback_id = {:#x}", callback_id);
}
return callback_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventSetHandleTimeout(s32 lib_ctx_id, s32 handle_id, u32 timeout) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, handle_id = {:#x}, timeout = {}",
lib_ctx_id, handle_id, timeout);
return setHandleTimeout(lib_ctx_id, handle_id, timeout);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventStartPushContextCallback(
s32 user_ctx_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
if (!push_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, push_ctx_id = {:#x}", user_ctx_id,
raw_id);
return startPushContextCallback(user_ctx_id, push_ctx_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterCallback(s32 user_ctx_id, s32 callback_id) {
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, callback_id = {:#x}", user_ctx_id,
callback_id);
return unregisterPushEventCallback(user_ctx_id, callback_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterPushContextCallback(s32 user_ctx_id,
s32 callback_id) {
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, callback_id = {:#x}", user_ctx_id,
callback_id);
return unregisterPushContextCallback(user_ctx_id, callback_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) {
if (!data || size == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, size = {:#x}", request_id, size);
return readData(request_id, data, size);
}
s32 PS4_SYSV_ABI
sceNpWebApi2SendMultipartRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
if (part_index <= 0 || !data || data_size == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_WARNING(Lib_NpWebApi2, "called, request_id = {:#x}, part_index = {}, data_size = {:#x}",
request_id, part_index, data_size);
return sendRequest(request_id, part_index, data, data_size, resp_info_option);
}
s32 PS4_SYSV_ABI
sceNpWebApi2SendRequest(s64 request_id, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, data_size = {:#x}", request_id, data_size);
return sendRequest(request_id, 0, data, data_size, resp_info_option);
}
s32 PS4_SYSV_ABI sceNpWebApi2SetMultipartContentType() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SetRequestTimeout(s64 request_id, u32 timeout) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, timeout = {}", request_id, timeout);
return setRequestTimeout(request_id, timeout);
}
s32 PS4_SYSV_ABI sceNpWebApi2Terminate(s32 lib_ctx_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}", lib_ctx_id);
return terminateLibraryContext(lib_ctx_id);
}
s32 PS4_SYSV_ABI Func_A9A31C5F6FBA6620() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_03D22863300D2B73() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_97296F7578AAD541() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_E0DF39A36F087DB9() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
Libraries::Np::NpManager::RegisterNpCallback("npwebapi2_push", processPushEvents);
LIB_FUNCTION("zpiPsH7dbFQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2AbortRequest);
LIB_FUNCTION("egOOvrnF6mI", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddHttpRequestHeader);
LIB_FUNCTION("Io7kh1LHDoM", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddMultipartPart);
LIB_FUNCTION("MgsTa76wlEk", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddWebTraceTag);
LIB_FUNCTION("3Tt9zL3tkoc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CheckTimeout);
LIB_FUNCTION("+nz1Vq-NrDA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2CreateMultipartRequest);
LIB_FUNCTION("3EI-OSJ65Xc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CreateRequest);
LIB_FUNCTION("sk54bi6FtYM", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2CreateUserContext);
LIB_FUNCTION("vvzWO-DvG1s", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2DeleteRequest);
LIB_FUNCTION("9X9+cneTGUU", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2DeleteUserContext);
LIB_FUNCTION("hksbskNToEA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetHttpResponseHeaderValue);
LIB_FUNCTION("HwP3aM+c85c", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetHttpResponseHeaderValueLength);
LIB_FUNCTION("Xweb+naPZ8Y", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetMemoryPoolStats);
LIB_FUNCTION("+o9816YQhqQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Initialize);
LIB_FUNCTION("dowMWFgowXY", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2InitializeForPresence);
LIB_FUNCTION("qmINYLuqzaA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntCreateRequest);
LIB_FUNCTION("zXaFo7euxsQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2IntInitialize);
LIB_FUNCTION("9KSGFMRnp3k", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntInitialize2);
LIB_FUNCTION("2hlBNB96saE", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntPushEventCreateCtxIndFilter);
LIB_FUNCTION("1OLgvahaSco", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventAbortHandle);
LIB_FUNCTION("MsaFhR+lPE4", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreateFilter);
LIB_FUNCTION("WV1GwM32NgY", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreateHandle);
LIB_FUNCTION("NNVf18SlbT8", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreatePushContext);
LIB_FUNCTION("KJdPcOGmK58", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeleteFilter);
LIB_FUNCTION("fIATVMo4Y1w", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeleteHandle);
LIB_FUNCTION("QafxeZM3WK4", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeletePushContext);
LIB_FUNCTION("fY3QqeNkF8k", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventRegisterCallback);
LIB_FUNCTION("lxtHJMwBsaU", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventRegisterPushContextCallback);
LIB_FUNCTION("KWkc6Q3tjXc", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventSetHandleTimeout);
LIB_FUNCTION("AAj9X+4aGYA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventStartPushContextCallback);
LIB_FUNCTION("hOnIlcGrO6g", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventUnregisterCallback);
LIB_FUNCTION("PmyrbbJSFz0", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventUnregisterPushContextCallback);
LIB_FUNCTION("OOY9+ObfKec", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2ReadData);
LIB_FUNCTION("NKCwS8+5Fx8", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SendMultipartRequest);
LIB_FUNCTION("lQOCF84lvzw", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2SendRequest);
LIB_FUNCTION("bltDCAskmfE", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SetMultipartContentType);
LIB_FUNCTION("TjAutbrkr60", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SetRequestTimeout);
LIB_FUNCTION("bEvXpcEk200", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Terminate);
LIB_FUNCTION("qaMcX2+6ZiA", "libSceNpWebApi2", 1, "libSceNpWebApi2", Func_A9A31C5F6FBA6620);
LIB_FUNCTION("A9IoYzANK3M", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_03D22863300D2B73);
LIB_FUNCTION("lylvdXiq1UE", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_97296F7578AAD541);
LIB_FUNCTION("4N85o28Ifbk", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_E0DF39A36F087DB9);
};
} // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpWebApi2 {
struct OrbisNpWebApi2IntInitializeArgs {
s32 lib_http_ctx_id;
s32 reserved;
u64 pool_size;
const char* name;
u64 struct_size;
};
struct OrbisNpWebApi2IntInitialize2Args {
s32 lib_http_ctx_id;
s32 reserved;
u64 pool_size;
const char* name;
u32 push_config_group;
s32 reserved2;
u64 struct_size;
};
struct OrbisNpWebApi2ContentParameter {
u64 content_length;
const char* content_type;
u8 reserved[16];
};
struct OrbisNpWebApi2MemoryPoolStats {
u64 pool_size;
u64 max_inuse_size;
u64 current_inuse_size;
s32 reserved;
};
struct OrbisNpWebApi2ResponseInformationOption {
s32 http_status;
s32 reserved;
char* error_object;
u64 error_object_size;
u64 response_data_size;
};
struct OrbisNpWebApi2PushEventDataType {
char val[65];
};
struct OrbisNpWebApi2PushEventExtdDataKey {
char val[33];
};
struct OrbisNpWebApi2PushEventFilterParameter {
OrbisNpWebApi2PushEventDataType data_type;
OrbisNpWebApi2PushEventExtdDataKey* extd_data_key;
u64 extd_data_key_num;
};
struct OrbisNpWebApi2PushEventExtdData {
OrbisNpWebApi2PushEventExtdDataKey extd_data_key;
char* data;
u64 data_len;
};
using OrbisNpWebApi2PushEventCallback = void PS4_SYSV_ABI (*)(
s32 user_ctx_id, s32 callback_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label, const OrbisNpPeerAddressA* to,
const OrbisNpOnlineId* to_online_id, const OrbisNpPeerAddressA* from,
const OrbisNpOnlineId* from_online_id, const OrbisNpWebApi2PushEventDataType* data_type,
const char* data, u64 data_len, const OrbisNpWebApi2PushEventExtdData* extd_data,
u64 extd_data_num, void* user_arg);
enum OrbisNpWebApi2PushEventPushContextCallbackType {
Unknown = -1,
Received = 0,
Dropped = 1,
};
struct OrbisNpWebApi2PushEventPushContextId {
char uuid[37];
};
using OrbisNpWebApi2PushEventPushContextCallback = void PS4_SYSV_ABI (*)(
s32 user_ctx_id, s32 callback_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id,
OrbisNpWebApi2PushEventPushContextCallbackType cb_type, const char* np_service_name,
OrbisNpServiceLabel np_service_label, const OrbisNpPeerAddressA* to,
const OrbisNpOnlineId* to_online_id, const OrbisNpPeerAddressA* from,
const OrbisNpOnlineId* from_online_id, const OrbisNpWebApi2PushEventDataType* data_type,
const char* data, u64 data_len, const OrbisNpWebApi2PushEventExtdData* extd_data,
u64 extd_data_num, void* user_arg);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,630 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_web_api2/np_web_api2_context.h"
namespace Libraries::Np::NpWebApi2 {
u32 g_current_push_event_handle_id{};
u32 g_current_push_event_filter_id{};
u32 g_current_push_event_callback_id{};
u32 g_current_push_context_callback_id{};
u32 g_current_user_context_id{};
u64 g_current_request_id{};
void LibraryContext::CheckTimeout() {
u64 time = Libraries::Kernel::sceKernelGetProcessTime();
std::scoped_lock lk{this->lock};
for (auto& [user_ctx_id, user_ctx] : this->user_contexts) {
user_ctx->CheckTimeout();
}
for (auto& [handle_id, handle] : this->push_event_handles) {
handle->CheckTimeout(time);
}
}
s32 LibraryContext::CreateUserContext(Libraries::UserService::OrbisUserServiceUserId user_id) {
std::scoped_lock lk{this->lock};
if (this->user_contexts.size() >= 0x10000) {
LOG_ERROR(Lib_NpWebApi2, "Too many user contexts");
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_MAX;
}
s32 actual_user_ctx_id = 0;
do {
g_current_user_context_id++;
if (g_current_user_context_id >= 0x10000) {
g_current_user_context_id = 1;
}
actual_user_ctx_id = (this->id << 0x10) | g_current_user_context_id;
} while (this->user_contexts.contains(actual_user_ctx_id));
this->user_contexts[actual_user_ctx_id] = new UserContext(this, actual_user_ctx_id, user_id);
return actual_user_ctx_id;
}
s32 LibraryContext::CreatePushEventHandle() {
std::scoped_lock lk{this->lock};
if (g_current_push_event_handle_id + 1 < 0xf0000000) {
++g_current_push_event_handle_id;
} else {
g_current_push_event_handle_id = 1;
}
s32 new_handle_id = g_current_push_event_handle_id;
this->push_event_handles[new_handle_id] = new PushEventHandle(new_handle_id);
return new_handle_id;
}
PushEventHandle* LibraryContext::GetPushEventHandle(s32 handle_id) {
std::scoped_lock lk{lock};
if (!push_event_handles.contains(handle_id)) {
return nullptr;
}
PushEventHandle* handle = push_event_handles[handle_id];
handle->AddUser();
return handle;
}
s32 LibraryContext::DeletePushEventHandle(PushEventHandle* handle) {
this->Lock();
s32 handle_id = handle->GetId();
if (handle->IsDeleted()) {
this->Unlock();
LOG_ERROR(Lib_NpWebApi2, "handle with id {:#x} is already deleted", handle_id);
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
handle->MarkForDeletion();
handle->Abort();
while (handle->IsBusy()) {
handle->RemoveUser();
this->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
this->Lock();
handle->AddUser();
}
if (push_event_handles.contains(handle_id)) {
push_event_handles.erase(handle_id);
}
delete handle;
return ORBIS_OK;
}
s32 LibraryContext::CreatePushEventFilter(
s32 handle_id, const char* np_service_name, OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param, u64 filter_param_num,
bool internal) {
std::scoped_lock lk{this->lock};
if (!this->push_event_handles.contains(handle_id)) {
LOG_ERROR(Lib_NpWebApi2, "No push event handle with id {:#x}", handle_id);
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
PushEventHandle* handle = this->push_event_handles[handle_id];
handle->AddUser();
if (g_current_push_event_filter_id + 1 < 0xf0000000) {
++g_current_push_event_filter_id;
} else {
g_current_push_event_filter_id = 1;
}
s32 new_filter_id = g_current_push_event_filter_id;
this->push_event_filters[new_filter_id] =
new PushEventFilter(new_filter_id, np_service_name, np_service_label, internal);
PushEventFilter* filter = this->push_event_filters[new_filter_id];
s32 result = filter->Initialize(handle, filter_param, filter_param_num);
if (result < 0) {
// Failed to initialize filter.
delete filter;
this->push_event_filters.erase(new_filter_id);
handle->RemoveUser();
return result;
}
handle->RemoveUser();
return new_filter_id;
}
UserContext* LibraryContext::GetUserContext(s32 user_ctx_id) {
std::scoped_lock lk{this->lock};
if (!this->user_contexts.contains(user_ctx_id)) {
return nullptr;
}
UserContext* user_ctx = this->user_contexts.at(user_ctx_id);
if (user_ctx->IsDeleted()) {
return nullptr;
}
user_ctx->AddUser();
return user_ctx;
}
UserContext* LibraryContext::GetUserContextByUserId(
Libraries::UserService::OrbisUserServiceUserId user_id) {
if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
return nullptr;
}
std::scoped_lock lk{this->lock};
for (auto& [user_ctx_id, user_ctx] : this->user_contexts) {
if (user_ctx->GetUserId() == user_id) {
user_ctx->AddUser();
return user_ctx;
}
}
return nullptr;
}
s32 PS4_SYSV_ABI internalPreSendCallback(s32 http_request_id, s32 ssl_id,
Libraries::Http2::OrbisHttp2PreSendCallbackData* data,
void* user_arg) {
s64 user_ctx_id = reinterpret_cast<s64>(user_arg);
LOG_ERROR(Lib_NpWebApi2,
"Unimplemented, http_request_id = {:#x}, ssl_id = {:#x}, user_ctx_id = {:#x}",
http_request_id, ssl_id, user_ctx_id);
return ORBIS_OK;
}
s32 UserContext::Initialize() {
const char* name = this->parent_ctx->GetName();
char sw_version[8];
std::memset(sw_version, 0, sizeof(sw_version));
Libraries::Np::NpCommon::sceNpGetSdkVersion(sw_version);
char user_agent_buf[0x40];
std::memset(user_agent_buf, 0, sizeof(user_agent_buf));
if (name) {
std::snprintf(user_agent_buf, sizeof(user_agent_buf), "NpWebApi2/%s (%s)", sw_version,
name);
} else {
std::snprintf(user_agent_buf, sizeof(user_agent_buf), "NpWebApi2/%s", sw_version);
}
this->user_agent = std::string{user_agent_buf};
s32 http_ctx_id = this->parent_ctx->GetHttpCtxId();
http_template_id = Libraries::Http2::sceHttp2CreateTemplate(
http_ctx_id, user_agent_buf,
Libraries::Http2::OrbisHttp2HttpVersion::ORBIS_HTTP2_VERSION_2_0, 0);
if (http_template_id < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to create HTTP template, error = {:#x}", http_template_id);
return http_template_id;
}
s32 result = Libraries::Http2::sceHttp2SetPreSendCallback(
http_template_id, internalPreSendCallback, reinterpret_cast<void*>(this->id));
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to set pre-send callback, error = {:#x}", result);
return result;
}
return result;
}
void UserContext::CheckTimeout() {
parent_ctx->Lock();
for (auto& [request_id, request] : requests) {
request->CheckTimeout();
}
parent_ctx->Unlock();
}
s32 UserContext::CreatePushEventCallback(s32 filter_id, OrbisNpWebApi2PushEventCallback cb_func,
void* user_arg) {
this->Lock();
if (g_current_push_event_callback_id + 1 < 0xf0000000) {
++g_current_push_event_callback_id;
} else {
g_current_push_event_callback_id = 1;
}
s32 new_callback_id = g_current_push_event_callback_id;
this->push_event_callbacks[new_callback_id] =
new PushEventCallback(new_callback_id, filter_id, cb_func, user_arg, false);
this->Unlock();
return new_callback_id;
}
s32 UserContext::DeletePushEventCallback(s32 callback_id) {
this->Lock();
if (!this->push_event_callbacks.contains(callback_id)) {
this->Unlock();
LOG_ERROR(Lib_NpWebApi2, "No push event callback with id {:#x}", callback_id);
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND;
}
PushEventCallback* callback = this->push_event_callbacks[callback_id];
while (callback->is_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(100000);
this->Lock();
}
delete callback;
this->push_event_callbacks.erase(callback_id);
this->Unlock();
return ORBIS_OK;
}
void UserContext::CreatePushContext(OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
this->Lock();
while (this->push_contexts_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(10000);
this->Lock();
}
this->push_contexts_busy = true;
PushEventPushContext* push_ctx = new PushEventPushContext(5000, this);
push_ctx->Initialize();
OrbisNpWebApi2PushEventPushContextId* new_id = push_ctx->GetId();
std::memcpy(push_ctx_id, new_id, sizeof(*push_ctx_id));
s64 raw_id = push_ctx->GetFakeId();
this->push_contexts[raw_id] = push_ctx;
this->push_contexts_busy = false;
this->Unlock();
}
PushEventPushContext* UserContext::GetPushContext(
const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
parent_ctx->Lock();
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
if (!push_contexts.contains(raw_id)) {
return nullptr;
}
PushEventPushContext* push_ctx = push_contexts[raw_id];
parent_ctx->Unlock();
return push_ctx;
}
void UserContext::DeletePushContext(PushEventPushContext* push_ctx) {
this->Lock();
while (this->push_contexts_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(10000);
this->Lock();
}
this->push_contexts_busy = true;
while (push_ctx->CallbackRunning()) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(100000);
this->Lock();
}
s64 raw_id = push_ctx->GetFakeId();
this->push_contexts.erase(raw_id);
delete push_ctx;
this->push_contexts_busy = false;
this->Unlock();
}
s32 UserContext::CreatePushContextCallback(s32 filter_id,
OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg) {
this->Lock();
if (g_current_push_context_callback_id + 1 < 0xf0000000) {
++g_current_push_context_callback_id;
} else {
g_current_push_context_callback_id = 1;
}
s32 new_callback_id = g_current_push_context_callback_id;
this->push_context_callbacks[new_callback_id] =
new PushEventPushContextCallback(new_callback_id, filter_id, cb_func, user_arg, false);
this->Unlock();
return new_callback_id;
}
s32 UserContext::DeletePushContextCallback(s32 callback_id) {
this->Lock();
if (!this->push_context_callbacks.contains(callback_id)) {
this->Unlock();
LOG_ERROR(Lib_NpWebApi2, "No push event push context callback with id {:#x}", callback_id);
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND;
}
PushEventPushContextCallback* callback = this->push_context_callbacks[callback_id];
while (callback->is_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(100000);
this->Lock();
}
delete callback;
this->push_context_callbacks.erase(callback_id);
this->Unlock();
return ORBIS_OK;
}
s32 UserContext::CreateRequest(const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter,
bool multipart, Request** request) {
this->Lock();
s64 actual_request_id = 0;
do {
g_current_request_id++;
if (g_current_request_id >> 0x20 != 0) {
g_current_request_id = 1;
}
actual_request_id = (static_cast<s64>(this->id) << 0x20) | g_current_request_id;
} while (this->requests.contains(actual_request_id));
if (content_parameter) {
this->requests[actual_request_id] =
new Request(this->parent_ctx, actual_request_id, api_group, path, method, multipart,
content_parameter);
} else {
this->requests[actual_request_id] =
new Request(this->parent_ctx, actual_request_id, api_group, path, method, multipart);
}
*request = this->requests[actual_request_id];
this->Unlock();
return ORBIS_OK;
}
Request* UserContext::GetRequest(s64 request_id) {
this->Lock();
if (!this->requests.contains(request_id)) {
this->Unlock();
return nullptr;
}
Request* request = this->requests[request_id];
request->AddUser();
this->Unlock();
return request;
}
bool UserContext::HasBusyRequests() {
this->Lock();
for (auto& [request_id, request] : this->requests) {
request->AddUser();
bool busy = request->IsBusy();
request->RemoveUser();
if (busy) {
this->Unlock();
return true;
}
}
this->Unlock();
return false;
}
void UserContext::AbortAllRequests() {
this->Lock();
for (auto& [request_id, request] : this->requests) {
request->Abort();
}
this->Unlock();
}
void UserContext::Delete() {
if (this->http_template_id != 0) {
Libraries::Http2::sceHttp2DeleteTemplate(this->http_template_id);
}
this->parent_ctx->RemoveUserContext(this->id);
this->RemoveUser();
for (auto& [request_id, request] : this->requests) {
request->Delete(nullptr);
}
this->requests.clear();
for (auto& [push_ctx_id, push_ctx] : this->push_contexts) {
delete push_ctx;
}
this->push_contexts.clear();
for (auto& [callback_id, callback] : this->push_event_callbacks) {
delete callback;
}
this->push_event_callbacks.clear();
for (auto& [callback_id, callback] : this->push_context_callbacks) {
delete callback;
}
this->push_context_callbacks.clear();
delete this;
}
static bool IsInternalHeader(const char* header_name) {
std::string lower_name{header_name};
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
[](char c) { return std::tolower(c); });
// These headers are all skipped if the request's library context
// was not created through the IntInitialize functions.
return lower_name.compare("content-type") == 0 || lower_name.compare("user-agent") == 0 ||
lower_name.compare("authorization") == 0 ||
lower_name.compare("x-psn-np-title-id") == 0 ||
lower_name.compare("x-psn-np-title-token") == 0 ||
lower_name.compare("x-psn-request-id") == 0 ||
lower_name.compare("x-psn-sdk-ver") == 0 ||
lower_name.compare("x-psn-debug-settings") == 0 || lower_name.compare("npdebug") == 0 ||
lower_name.compare("x-psn-trc-check-notification-enabled") == 0 ||
lower_name.compare("x-psn-webtrace-enabled") == 0 ||
lower_name.compare("x-psn-npwebapi2-context") == 0 ||
lower_name.compare("x-psn-fake-rate-limit-enabled") == 0 ||
lower_name.compare("x-psn-fake-rate-limit-targets") == 0;
}
s32 Request::AddHttpRequestHeader(const char* field_name, const char* field_value) {
if (!this->parent_ctx->IsInternal() && IsInternalHeader(field_name)) {
LOG_WARNING(Lib_NpWebApi2, "This is not an internal context, skipping field name {}",
field_name);
return ORBIS_OK;
}
this->Lock();
this->http_headers.emplace_back(new HttpRequestHeader(field_name, field_value));
this->Unlock();
return ORBIS_OK;
}
s32 Request::CreateHttpRequest(s32 http_template_id, const char* url) {
const char* method = this->method.empty() ? "UNKNOWN" : this->method.data();
s32 http_request_id = Libraries::Http2::sceHttp2CreateRequestWithURL(http_template_id, method,
url, this->content_length);
if (http_request_id < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to create Http2 request, error = {:#x}", http_request_id);
return http_request_id;
}
this->http_request_id = http_request_id;
s32 result = Libraries::Http2::sceHttp2AddRequestHeader(http_request_id, "Content-Type",
this->content_type.data(), 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to add Content-Type request header, error = {:#x}",
result);
return result;
}
UserContext* user_ctx = this->parent_ctx->GetUserContext(this->id >> 0x20);
const std::string bearer = NpHandler::GetInstance().GetBearerToken(user_ctx->GetUserId());
if (!bearer.empty()) {
const std::string auth_value = "Bearer " + bearer;
result = Libraries::Http2::sceHttp2AddRequestHeader(http_request_id, "Authorization",
auth_value.data(), 0);
}
if (bearer.empty() || result < 0) {
LOG_WARNING(Lib_NpWebApi2, "Failed to add Authorization request header");
}
// This would go on to add several other PSN-specific headers.
// As shadNet does not appear to use them, the logic is skipped for now.
for (HttpRequestHeader* header : this->http_headers) {
s32 result = Libraries::Http2::sceHttp2AddRequestHeader(
http_request_id, header->field_name.data(), header->field_value.data(), 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2,
"Failed to add request header, name = {}, value = {}, error = {:#x}",
header->field_name.data(), header->field_value.data(), result);
user_ctx->RemoveUser();
return result;
}
}
user_ctx->RemoveUser();
return result;
}
s32 Request::SendHttpRequest(void* data, u64 data_size) {
s32 result = 0;
if (this->content_length != 0 && this->sent_data == 0) {
// Real library seems to do some calculations, some parts including IPC calls.
// Not entirely sure how accurate this is in practice.
result = Libraries::Http2::sceHttp2SetRequestContentLength(this->http_request_id,
this->content_length);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to set content length, error = {:#x}", result);
return result;
}
}
if (!data || data_size == 0 || this->content_length == 0) {
result = Libraries::Http2::sceHttp2SendRequest(this->http_request_id, nullptr, 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to send request, error = {:#x}", result);
return result;
}
}
u64 actual_size = std::min<u64>(data_size, this->content_length - this->sent_data);
if (this->content_length <= this->sent_data) {
// Nothing left to send
return result;
}
result = Libraries::Http2::sceHttp2SendRequest(this->http_request_id, data, actual_size);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to send request, error = {:#x}", result);
return result;
}
this->sent_data += actual_size;
return result;
}
s32 Request::GetAllHttpResponseHeaders() {
char* header = nullptr;
u64 size = 0;
s32 result =
Libraries::Http2::sceHttp2GetAllResponseHeaders(this->http_request_id, &header, &size);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to get response headers, error = {:#x}", result);
return result;
}
this->http_response_headers = header;
this->http_response_header_size = size;
return result;
}
s32 Request::ParseHttpResponseHeaders(const char* field_name, char* value, u64 value_size,
u64* value_size_out) {
const char* temp_val{};
u64 temp_size{};
s32 result = Libraries::Http::sceHttpParseResponseHeader(this->http_response_headers,
this->http_response_header_size,
field_name, &temp_val, &temp_size);
if (result >= 0) {
if (value) {
std::memset(value, 0, value_size);
u64 size = std::min<u64>(value_size, temp_size);
std::memcpy(value, temp_val, size);
}
if (value_size_out) {
*value_size_out = temp_size;
}
result = ORBIS_OK;
}
return result;
}
s32 Request::Abort() {
this->Lock();
s32 result = 0;
if (this->Aborted()) {
// Nothing to do.
this->Unlock();
return result;
}
this->aborted = true;
// Real library has multiple states, we don't since our logic is simpler.
if (this->send_state == 3) {
// In the middle of an Http request, that needs aborting.
result = Libraries::Http2::sceHttp2AbortRequest(this->http_request_id);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to abort http request, error = {:#x}", result);
}
}
this->Unlock();
return result;
}
s32 Request::Delete(s32* http_request) {
for (HttpRequestHeader* header : this->http_headers) {
delete header;
}
this->http_headers.clear();
if (!http_request && this->http_request_id) {
Libraries::Http2::sceHttp2DeleteRequest(this->http_request_id);
} else {
*http_request = this->http_request_id;
}
while (this->user_count > 0) {
Libraries::Kernel::sceKernelUsleep(50000);
}
delete this;
return ORBIS_OK;
}
}; // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,463 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/kernel/time.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/np/np_web_api2/np_web_api2_internal.h"
#include "core/libraries/np/np_web_api2/np_web_api2_push_event.h"
#include "core/libraries/system/userservice.h"
#include <atomic>
#include <map>
#include <mutex>
#include <string>
#include <vector>
namespace Libraries::Np::NpWebApi2 {
class UserContext;
class Request;
class LibraryContext {
public:
LibraryContext(s32 ctx_id, s32 type, s32 http_ctx_id, u64 pool_size)
: id(ctx_id), type(static_cast<LibraryContextType>(type)), http_ctx_id(http_ctx_id),
pool_size(pool_size) {}
LibraryContext(s32 ctx_id, s32 type, s32 http_ctx_id, u64 pool_size, const char* name)
: id(ctx_id), type(static_cast<LibraryContextType>(type)), http_ctx_id(http_ctx_id),
pool_size(pool_size), name(name) {}
void Lock() {
lock.lock();
}
void Unlock() {
lock.unlock();
}
void AddUser() {
std::scoped_lock lk{lock};
user_count++;
}
void RemoveUser() {
std::scoped_lock lk{lock};
user_count--;
}
s32 GetId() {
return id;
}
s32 GetHttpCtxId() {
return http_ctx_id;
}
u64 GetPoolSize() {
return pool_size;
}
const char* GetName() {
return name.empty() ? nullptr : name.data();
}
bool IsDebug() {
return type == LibraryContextType::Debug;
}
bool IsNormal() {
return type == LibraryContextType::Normal;
}
bool IsInternal() {
return type == LibraryContextType::Internal;
}
bool IsPresence() {
return type == LibraryContextType::Presence;
}
bool IsDeleted() {
return deleting;
}
void MarkForDeletion() {
deleting = true;
}
bool IsBusy() {
std::scoped_lock lk{lock};
return user_count > 1;
}
void CheckTimeout();
s32 CreatePushEventHandle();
PushEventHandle* GetPushEventHandle(s32 handle_id);
void SetHandleEndTime(s32 handle_id) {
std::scoped_lock lk{lock};
if (push_event_handles.contains(handle_id)) {
auto& handle = push_event_handles[handle_id];
handle->SetEndTime();
}
}
void AbortAllPushEventHandles() {
std::scoped_lock lk{lock};
for (auto& [handle_id, handle] : push_event_handles) {
abortPushEventHandle(id, handle_id);
}
}
bool HasBusyPushEventHandles() {
std::scoped_lock lk{lock};
for (auto& [handle_id, handle] : push_event_handles) {
if (handle->IsBusy()) {
return true;
}
}
return false;
}
s32 DeletePushEventHandle(PushEventHandle* handle);
void DeleteAllPushEventHandles() {
for (auto& [handle_id, handle] : push_event_handles) {
delete handle;
}
push_event_handles.clear();
}
s32 CreatePushEventFilter(s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num, bool internal);
PushEventFilter* GetPushEventFilter(s32 filter_id) {
std::scoped_lock lk{lock};
if (!push_event_filters.contains(filter_id)) {
return nullptr;
}
return push_event_filters[filter_id];
}
void DeletePushEventFilter(PushEventFilter* filter) {
std::scoped_lock lk{lock};
s32 filter_id = filter->GetId();
if (push_event_filters.contains(filter_id)) {
push_event_filters.erase(filter_id);
}
delete filter;
}
void DeleteAllPushEventFilters() {
for (auto& [filter_id, filter] : push_event_filters) {
delete filter;
}
push_event_filters.clear();
}
s32 CreateUserContext(Libraries::UserService::OrbisUserServiceUserId user_id);
UserContext* GetUserContext(s32 user_ctx_id);
UserContext* GetUserContextByUserId(Libraries::UserService::OrbisUserServiceUserId user_id);
void RemoveUserContext(s32 user_ctx_id) {
std::scoped_lock lk{lock};
if (user_contexts.contains(user_ctx_id)) {
user_contexts.erase(user_ctx_id);
}
}
void DeleteAllUserContexts() {
std::scoped_lock lk{lock};
for (auto& [user_ctx_id, user_ctx] : user_contexts) {
deleteUserContext(user_ctx_id);
}
user_contexts.clear();
}
private:
s32 id{};
s32 http_ctx_id{};
s32 user_count{};
enum LibraryContextType : s32 {
Debug = 0,
Normal = 1,
Internal = 2,
Presence = 3,
} type;
u64 pool_size{};
bool deleting{};
std::recursive_mutex lock{};
std::string name{};
std::map<s32, UserContext*> user_contexts{};
std::map<s32, PushEventHandle*> push_event_handles{};
std::map<s32, PushEventFilter*> push_event_filters{};
};
class UserContext {
public:
UserContext(LibraryContext* parent, s32 user_ctx_id,
Libraries::UserService::OrbisUserServiceUserId user_id)
: parent_ctx(parent), id(user_ctx_id), user_id(user_id) {}
void Lock() {
parent_ctx->Lock();
}
void Unlock() {
parent_ctx->Unlock();
}
void AddUser() {
Lock();
user_count++;
Unlock();
}
void RemoveUser() {
Lock();
user_count--;
Unlock();
}
bool IsDeleted() {
return deleting;
}
void MarkForDeletion() {
deleting = true;
}
bool IsBusy() {
parent_ctx->Lock();
s32 users = user_count;
parent_ctx->Unlock();
// Assumes caller is a user
return users > 1;
}
s32 GetId() {
return id;
}
Libraries::UserService::OrbisUserServiceUserId GetUserId() {
return user_id;
}
s32 GetHttpTemplateId() {
return http_template_id;
}
s32 Initialize();
void CheckTimeout();
s32 CreatePushEventCallback(s32 filter_id, OrbisNpWebApi2PushEventCallback cb_func,
void* user_arg);
s32 DeletePushEventCallback(s32 callback_id);
void CreatePushContext(OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
PushEventPushContext* GetPushContext(const OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
void DeletePushContext(PushEventPushContext* push_ctx_id);
s32 CreatePushContextCallback(s32 filter_id, OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg);
s32 DeletePushContextCallback(s32 callback_id);
s32 CreateRequest(const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
Request** request);
Request* GetRequest(s64 request_id);
bool HasBusyRequests();
void AbortAllRequests();
void RemoveRequest(s64 request_id) {
parent_ctx->Lock();
if (requests.contains(request_id)) {
requests.erase(request_id);
}
parent_ctx->Unlock();
}
void Delete();
private:
s32 id{};
Libraries::UserService::OrbisUserServiceUserId user_id{};
s32 user_count{};
s32 http_template_id{};
bool deleting{};
bool push_contexts_busy{};
LibraryContext* parent_ctx{};
std::string user_agent{};
std::map<s64, Request*> requests{};
std::map<s32, PushEventCallback*> push_event_callbacks{};
std::map<s64, PushEventPushContext*> push_contexts{};
std::map<s32, PushEventPushContextCallback*> push_context_callbacks{};
};
class Request {
public:
Request(LibraryContext* parent, s64 request_id, const char* api_group, const char* path,
const char* method, bool multipart)
: parent_ctx(parent), id(request_id), api_group(api_group), path(path), method(method),
multipart_supported(multipart) {}
Request(LibraryContext* parent, s64 request_id, const char* api_group, const char* path,
const char* method, bool multipart,
const OrbisNpWebApi2ContentParameter* content_parameter)
: parent_ctx(parent), id(request_id), api_group(api_group), path(path), method(method),
multipart_supported(multipart), content_type(content_parameter->content_type),
content_length(content_parameter->content_length) {}
void Lock() {
parent_ctx->Lock();
}
void Unlock() {
parent_ctx->Unlock();
}
void AddUser() {
Lock();
user_count++;
Unlock();
}
void RemoveUser() {
Lock();
user_count--;
Unlock();
}
s64 GetId() {
return id;
}
s32 GetHttpRequestId() {
return http_request_id;
}
bool IsMultipart() {
return multipart_supported;
}
bool HasSent() {
return sent;
}
void MarkSent() {
sent = true;
}
bool Aborted() {
return aborted;
}
bool Expired() {
return expired;
}
bool OutOfData() {
return content_length == sent_data;
}
void SetEndTime() {
if (timeout != 0 && end_time == 0) {
end_time = Libraries::Kernel::sceKernelGetProcessTime() + timeout;
}
}
void ClearEndTime() {
end_time = 0;
}
std::string& GetApiGroup() {
return api_group;
}
std::string& GetPath() {
return path;
}
std::string& GetMethod() {
return method;
}
std::string& GetContentType() {
return content_type;
}
void SetState(s32 state) {
send_state = state;
}
bool IsDeleted() {
return deleting;
}
void MarkForDeletion() {
deleting = true;
}
bool IsBusy() {
parent_ctx->Lock();
s32 users = user_count;
parent_ctx->Unlock();
// Assumes caller is a user
return users > 1;
}
s32 AddHttpRequestHeader(const char* field_name, const char* field_value);
void SetTimeout(u32 new_timeout) {
this->timeout = new_timeout;
}
void CheckTimeout() {
u64 time = Libraries::Kernel::sceKernelGetProcessTime();
if (!expired && end_time != 0 && end_time < time) {
expired = true;
Abort();
}
}
s32 CreateHttpRequest(s32 http_template_id, const char* url);
s32 SendHttpRequest(void* data, u64 data_size);
s32 GetAllHttpResponseHeaders();
s32 ParseHttpResponseHeaders(const char* field_name, char* value, u64 value_size,
u64* value_size_out);
s32 Abort();
s32 Delete(s32* http_request_id);
private:
s64 id{};
u64 content_length{};
char* http_response_headers{};
u64 http_response_header_size{};
s32 user_count{};
s32 http_request_id{};
u32 timeout{};
s32 sent_data{};
s32 send_state{};
bool multipart_supported{};
bool sent{};
bool aborted{};
bool expired{};
bool deleting{};
u64 end_time{};
LibraryContext* parent_ctx{};
std::string api_group{};
std::string path{};
std::string method{};
std::string content_type{};
struct HttpRequestHeader {
std::string field_name;
std::string field_value;
};
std::vector<HttpRequestHeader*> http_headers{};
};
}; // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,932 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_web_api2/np_web_api2_context.h"
#include "core/libraries/np/np_web_api2/np_web_api2_internal.h"
#include "core/libraries/system/userservice.h"
#include <map>
#include <mutex>
namespace Libraries::Np::NpWebApi2 {
std::recursive_mutex g_mutex{};
s32 g_current_lib_context_id{};
std::map<s32, LibraryContext*> g_lib_contexts{};
u64 g_last_timeout_check{};
s32 createLibraryContext(s32 http_ctx_id, s32 type, u64 pool_size, const char* name) {
std::scoped_lock lk{g_mutex};
if (g_lib_contexts.size() >= 0x8000) {
LOG_ERROR(Lib_NpWebApi2, "Too many library contexts");
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_MAX;
}
do {
g_current_lib_context_id++;
if (g_current_lib_context_id >= 0x8000) {
g_current_lib_context_id = 1;
}
} while (g_lib_contexts.contains(g_current_lib_context_id));
if (!name) {
g_lib_contexts[g_current_lib_context_id] =
new LibraryContext(g_current_lib_context_id, type, http_ctx_id, pool_size);
} else {
g_lib_contexts[g_current_lib_context_id] =
new LibraryContext(g_current_lib_context_id, type, http_ctx_id, pool_size, name);
}
return g_current_lib_context_id;
}
LibraryContext* getLibraryContext(s32 lib_ctx_id) {
std::scoped_lock lk{g_mutex};
if (!g_lib_contexts.contains(lib_ctx_id)) {
return nullptr;
}
LibraryContext* lib_ctx = g_lib_contexts.at(lib_ctx_id);
if (lib_ctx->IsDeleted()) {
return nullptr;
}
lib_ctx->AddUser();
return lib_ctx;
}
s32 getMemoryPoolStats(s32 lib_ctx_id, OrbisNpWebApi2MemoryPoolStats* stats) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
if (stats) {
memset(stats, 0, sizeof(*stats));
stats->pool_size = lib_ctx->GetPoolSize();
}
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 createPushEventHandle(s32 lib_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
s32 result = lib_ctx->CreatePushEventHandle();
lib_ctx->RemoveUser();
return result;
}
s32 setHandleTimeout(s32 lib_ctx_id, s32 handle_id, u32 timeout) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventHandle* handle = lib_ctx->GetPushEventHandle(handle_id);
if (!handle) {
LOG_ERROR(Lib_NpWebApi2, "No handle with id {:#x}", handle_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
lib_ctx->Lock();
handle->SetTimeout(timeout);
handle->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 abortPushEventHandle(s32 lib_ctx_id, s32 handle_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventHandle* handle = lib_ctx->GetPushEventHandle(handle_id);
if (!handle) {
LOG_ERROR(Lib_NpWebApi2, "No handle with id {:#x}", handle_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
lib_ctx->Lock();
handle->Abort();
lib_ctx->Unlock();
handle->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 deletePushEventHandle(s32 lib_ctx_id, s32 handle_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventHandle* handle = lib_ctx->GetPushEventHandle(handle_id);
if (!handle) {
LOG_ERROR(Lib_NpWebApi2, "No handle with id {:#x}", handle_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
s32 result = lib_ctx->DeletePushEventHandle(handle);
lib_ctx->RemoveUser();
return result;
}
s32 createPushEventFilter(s32 lib_ctx_id, s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num, bool internal) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->SetHandleEndTime(handle_id);
s32 result = lib_ctx->CreatePushEventFilter(handle_id, np_service_name, np_service_label,
filter_param, filter_param_num, internal);
lib_ctx->RemoveUser();
return result;
}
s32 deletePushEventFilter(s32 lib_ctx_id, s32 filter_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventFilter* filter = lib_ctx->GetPushEventFilter(filter_id);
if (!filter) {
LOG_ERROR(Lib_NpWebApi2, "No filter with id {:#x}", filter_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
lib_ctx->DeletePushEventFilter(filter);
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 terminateLibraryContext(s32 lib_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->Lock();
lib_ctx->DeleteAllUserContexts();
lib_ctx->AbortAllPushEventHandles();
if (lib_ctx->IsDeleted()) {
LOG_ERROR(Lib_NpWebApi2, "Library context with id {:#x} is already deleted", lib_ctx_id);
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->MarkForDeletion();
while (lib_ctx->IsBusy() || lib_ctx->HasBusyPushEventHandles()) {
lib_ctx->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
lib_ctx->Lock();
}
lib_ctx->Unlock();
lib_ctx->RemoveUser();
g_mutex.lock();
g_lib_contexts.erase(lib_ctx_id);
g_mutex.unlock();
lib_ctx->DeleteAllPushEventHandles();
lib_ctx->DeleteAllPushEventFilters();
delete lib_ctx;
return ORBIS_OK;
}
s32 createUserContext(s32 lib_ctx_id, Libraries::UserService::OrbisUserServiceUserId user_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContextByUserId(user_id);
if (user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "User context already exists for user id {}", user_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_ALREADY_EXIST;
}
s32 user_ctx_id = lib_ctx->CreateUserContext(user_id);
if (user_ctx_id < 0) {
// user_ctx_id contains an error code, return it.
lib_ctx->RemoveUser();
return user_ctx_id;
}
user_ctx = lib_ctx->GetUserContext(user_ctx_id);
user_ctx->RemoveUser();
s32 result = user_ctx->Initialize();
if (result < 0) {
// Failed to initialize user_ctx, destroy it.
lib_ctx->RemoveUserContext(user_ctx_id);
delete user_ctx;
}
lib_ctx->RemoveUser();
return user_ctx_id;
}
s32 deleteUserContext(s32 user_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->Lock();
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
user_ctx->AbortAllRequests();
if (user_ctx->IsDeleted()) {
LOG_ERROR(Lib_NpWebApi2, "User context with id {:#x} is already deleted", user_ctx_id);
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
user_ctx->MarkForDeletion();
while (user_ctx->IsBusy() || user_ctx->HasBusyRequests()) {
lib_ctx->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
lib_ctx->Lock();
}
user_ctx->Delete();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 registerPushEventCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventCallback cb_func, void* user_arg) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
if (!lib_ctx->GetPushEventFilter(filter_id)) {
LOG_ERROR(Lib_NpWebApi2, "No push event filter with id {:#x}", filter_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
s32 result = user_ctx->CreatePushEventCallback(filter_id, cb_func, user_arg);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 unregisterPushEventCallback(s32 user_ctx_id, s32 callback_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
s32 result = user_ctx->DeletePushEventCallback(callback_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 createPushContext(s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
user_ctx->CreatePushContext(push_ctx_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 startPushContextCallback(s32 user_ctx_id,
const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
PushEventPushContext* push_ctx = user_ctx->GetPushContext(push_ctx_id);
if (!push_ctx) {
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_ERROR(Lib_NpWebApi2, "No push context with id {}", raw_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_CONTEXT_NOT_FOUND;
}
push_ctx->Start();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 deletePushContext(s32 user_ctx_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
PushEventPushContext* push_ctx = user_ctx->GetPushContext(push_ctx_id);
if (!push_ctx) {
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_ERROR(Lib_NpWebApi2, "No push context with id {}", raw_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_CONTEXT_NOT_FOUND;
}
user_ctx->DeletePushContext(push_ctx);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 registerPushContextCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
if (!lib_ctx->GetPushEventFilter(filter_id)) {
LOG_ERROR(Lib_NpWebApi2, "No push event filter with id {:#x}", filter_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
s32 result = user_ctx->CreatePushContextCallback(filter_id, cb_func, user_arg);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 unregisterPushContextCallback(s32 user_ctx_id, s32 callback_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
s32 result = user_ctx->DeletePushContextCallback(callback_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
void processPushEvents() {
// LOG_ERROR(Lib_NpWebApi2, "(STUBBED)");
}
s32 createRequest(s32 user_ctx_id, const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
s64* request_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
if (std::strlen(api_group) >= 63) {
LOG_ERROR(Lib_NpWebApi2, "API group is too long");
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PARAMETER_TOO_LONG;
}
Request* request{};
s32 result =
user_ctx->CreateRequest(api_group, path, method, content_parameter, multipart, &request);
if (result < 0) {
// Request creation failed
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
if (request) {
delete request;
}
return result;
}
if (request_id) {
*request_id = request->GetId();
}
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 addHttpRequestHeader(s64 request_id, const char* field_name, const char* field_value) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
if (request->HasSent()) {
LOG_ERROR(Lib_NpWebApi2, "Request is already sent");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_AFTER_SEND;
}
s32 result = request->AddHttpRequestHeader(field_name, field_value);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 setRequestTimeout(s64 request_id, u32 timeout) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetTimeout(timeout);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
static s32 checkRequestStatus(Request* request, UserContext* user_ctx, LibraryContext* lib_ctx) {
if (request->Expired()) {
LOG_ERROR(Lib_NpWebApi2, "Request timed out");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_TIMEOUT;
}
if (request->Aborted()) {
LOG_ERROR(Lib_NpWebApi2, "Request aborted");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_ABORTED;
}
return ORBIS_OK;
}
s32 sendRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetEndTime();
if (request->IsMultipart()) {
if (part_index == 0) {
LOG_ERROR(Lib_NpWebApi2, "part_index 0 is prohibited for multipart requests");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_FUNCTION_CALL;
}
// TODO: Multipart logic
}
if (!request->HasSent()) {
request->MarkSent();
}
request->Lock();
request->SetState(0);
s32 result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
if (!EmulatorSettings.IsShadNetEnabled()) {
LOG_INFO(Lib_NpWebApi2, "Cannot send request, you are not signed in to shadNet");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN;
}
if (request->GetHttpRequestId() == 0) {
std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
s32 template_id = user_ctx->GetHttpTemplateId();
// Technically this should be base_url + api_group + path,
// but shadNet doesn't seem to use the api_group for endpoints?
std::string full_url = base_url + request->GetPath();
result = request->CreateHttpRequest(template_id, full_url.data());
if (result < 0) {
// Underlying request creation failed, return.
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
}
request->Lock();
request->SetState(3);
result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
if (request->IsMultipart()) {
// TODO: Multipart sends
// result = request->SendMultipartHttpRequest(data, data_size, part_index);
} else {
result = request->SendHttpRequest(data, data_size);
}
if (result >= 0 && request->OutOfData()) {
result = request->GetAllHttpResponseHeaders();
if (result >= 0) {
s32 http_request_id = request->GetHttpRequestId();
s32 http_status{};
result = Libraries::Http2::sceHttp2GetStatusCode(http_request_id, &http_status);
if (result >= 0) {
if (resp_info_option) {
resp_info_option->http_status = http_status;
}
if (http_status >= 400) {
result = http_status | 0x82f00000;
}
} else {
LOG_ERROR(Lib_NpWebApi2, "Failed to get http status code, error = {:#x}", result);
}
}
}
request->Lock();
request->SetState(0);
s32 result2 = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result2 != 0) {
// Aborted or timed out.
return result2;
}
request->ClearEndTime();
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 getHttpResponseHeaderData(s64 request_id, const char* field_name, char* value, u64 value_size,
u64* value_size_out) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
s32 result = request->ParseHttpResponseHeaders(field_name, value, value_size, value_size_out);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 readData(s64 request_id, void* data, u64 size) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetEndTime();
request->Lock();
request->SetState(3);
s32 result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
s32 http_req_id = request->GetHttpRequestId();
s32 result2 = Libraries::Http2::sceHttp2ReadData(http_req_id, data, size);
request->Lock();
request->SetState(0);
result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
request->ClearEndTime();
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result2;
}
s32 abortRequest(s64 request_id) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->RemoveUser();
s32 result = request->Abort();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 deleteRequest(s64 request_id) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->Lock();
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
if (request->IsDeleted()) {
// Already marked for deletion
LOG_ERROR(Lib_NpWebApi2, "Request with id {:#x} is already being deleted", request_id);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->MarkForDeletion();
request->Abort();
while (request->IsBusy()) {
lib_ctx->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
lib_ctx->Lock();
}
user_ctx->RemoveRequest(request_id);
request->RemoveUser();
s32 http_request_id = 0;
request->Delete(&http_request_id);
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
if (http_request_id != 0) {
Libraries::Http2::sceHttp2DeleteRequest(http_request_id);
}
return ORBIS_OK;
}
void checkTimeout() {
u64 current_time = Libraries::Kernel::sceKernelGetProcessTime();
if (current_time < g_last_timeout_check + 1000) {
// Too soon, skip check.
return;
}
std::scoped_lock lk{g_mutex};
g_last_timeout_check = current_time;
for (auto& [lib_ctx_id, lib_ctx] : g_lib_contexts) {
lib_ctx->CheckTimeout();
}
}
}; // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpWebApi2 {
s32 createLibraryContext(s32 http_ctx_id, s32 type, u64 pool_size, const char* name);
s32 getMemoryPoolStats(s32 lib_ctx_id, OrbisNpWebApi2MemoryPoolStats* stats);
s32 createPushEventHandle(s32 lib_ctx_id);
s32 setHandleTimeout(s32 lib_ctx_id, s32 handle_id, u32 timeout);
s32 abortPushEventHandle(s32 lib_ctx_id, s32 handle_id);
s32 deletePushEventHandle(s32 lib_ctx_id, s32 handle_id);
s32 createPushEventFilter(s32 lib_ctx_id, s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num, bool internal);
s32 deletePushEventFilter(s32 lib_ctx_id, s32 filter_id);
s32 terminateLibraryContext(s32 lib_ctx_id);
s32 createUserContext(s32 lib_ctx_id, Libraries::UserService::OrbisUserServiceUserId user_id);
s32 deleteUserContext(s32 user_ctx_id);
s32 registerPushEventCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventCallback cb_func, void* user_arg);
s32 unregisterPushEventCallback(s32 user_ctx_id, s32 callback_id);
s32 createPushContext(s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
s32 startPushContextCallback(s32 user_ctx_id,
const OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
s32 deletePushContext(s32 user_ctx_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
s32 registerPushContextCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventPushContextCallback cb_func, void* user_arg);
s32 unregisterPushContextCallback(s32 user_ctx_id, s32 callback_id);
void processPushEvents();
s32 createRequest(s32 user_ctx_id, const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
s64* request_id);
s32 addHttpRequestHeader(s64 request_id, const char* field_name, const char* field_value);
s32 setRequestTimeout(s64 request_id, u32 timeout);
s32 sendRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option);
s32 getHttpResponseHeaderData(s64 request_id, const char* field_name, char* value, u64 value_size,
u64* value_size_out);
s32 readData(s64 request_id, void* data, u64 size);
s32 abortRequest(s64 request_id);
s32 deleteRequest(s64 request_id);
void checkTimeout();
}; // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_web_api2/np_web_api2_push_event.h"
namespace Libraries::Np::NpWebApi2 {
s64 g_current_push_event_push_context_id{};
s32 PushEventFilter::Initialize(PushEventHandle* handle,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num) {
if (handle->IsDeleted()) {
LOG_ERROR(Lib_NpWebApi2, "handle {:#x} is invalid", handle->GetId());
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
if (handle->IsTimedout()) {
LOG_ERROR(Lib_NpWebApi2, "handle {:#x} has timed out", handle->GetId());
return ORBIS_NP_WEBAPI2_ERROR_TIMEOUT;
}
if (handle->IsAborted()) {
LOG_ERROR(Lib_NpWebApi2, "handle {:#x} was aborted", handle->GetId());
return ORBIS_NP_WEBAPI2_ERROR_ABORTED;
}
std::vector<std::vector<OrbisNpWebApi2PushEventExtdDataKey>> copy_storage{};
for (u64 i = 0; i < filter_param_num; i++) {
OrbisNpWebApi2PushEventFilterParameter new_param{};
memcpy(&new_param, &filter_param[i], sizeof(OrbisNpWebApi2PushEventFilterParameter));
if (filter_param[i].extd_data_key != nullptr && filter_param[i].extd_data_key_num != 0) {
std::vector<OrbisNpWebApi2PushEventExtdDataKey> data_keys{};
for (u64 j = 0; j < filter_param[i].extd_data_key_num; j++) {
OrbisNpWebApi2PushEventExtdDataKey new_key{};
memcpy(&new_key, &filter_param[i].extd_data_key[j],
sizeof(OrbisNpWebApi2PushEventExtdDataKey));
data_keys.emplace_back(new_key);
}
new_param.extd_data_key = data_keys.data();
copy_storage.emplace_back(data_keys);
}
this->filter_params.emplace_back(new_param);
}
return ORBIS_OK;
}
void PushEventPushContext::Initialize() {
// In the real library, there's a whole process of registering this with NpManager's Push2 API.
// For now, we'll just create the uuid for this push context.
// As for the ID itself, for now we'll cheat a bit.
// Real library does a mostly random string, but it's easier to just use an actual index.
++g_current_push_event_push_context_id;
if (g_current_push_event_push_context_id < 0) {
g_current_push_event_push_context_id = 1;
}
s64 new_id = g_current_push_event_push_context_id;
std::memset(&this->id, 0, sizeof(this->id));
std::memcpy(&this->id, &new_id, sizeof(s64));
}
}; // namespace Libraries::Np::NpWebApi2

View File

@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
namespace Libraries::Np::NpWebApi2 {
class UserContext;
class PushEventHandle {
public:
PushEventHandle(s32 new_id) : id(new_id) {}
s32 GetId() {
return id;
}
void SetTimeout(u32 new_timeout) {
timeout = new_timeout;
}
void AddUser() {
user_count++;
}
void RemoveUser() {
user_count--;
}
void Abort() {
aborted = true;
}
bool IsAborted() {
return aborted;
}
void MarkForDeletion() {
deleting = true;
}
bool IsDeleted() {
return deleting;
}
void SetEndTime() {
if (timeout != 0) {
end_time = Libraries::Kernel::sceKernelGetProcessTime() + timeout;
}
}
void ClearEndTime() {
end_time = 0;
}
u64 GetEndTime() {
return end_time;
}
bool IsTimedout() {
return timed_out;
}
void CheckTimeout(u64 time) {
if (!timed_out && end_time != 0 && end_time < time) {
timed_out = true;
Abort();
}
}
bool IsBusy() {
return user_count > 1;
}
private:
s32 id{};
u32 timeout{};
s32 user_count{};
bool aborted{};
bool deleting{};
bool timed_out{};
u64 end_time{};
};
class PushEventFilter {
public:
PushEventFilter(s32 new_id, const char* service_name, OrbisNpServiceLabel service_label,
bool is_internal)
: id(new_id), np_service_name(service_name ? service_name : ""),
np_service_label(service_label), internal(is_internal) {}
s32 Initialize(PushEventHandle* handle,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num);
s32 GetId() {
return id;
}
private:
s32 id{};
OrbisNpServiceLabel np_service_label{};
bool internal{};
std::vector<OrbisNpWebApi2PushEventFilterParameter> filter_params{};
std::vector<std::vector<OrbisNpWebApi2PushEventExtdDataKey>> data_key_copy_storage{};
std::string np_service_name{};
};
struct PushEventCallback {
s32 id;
s32 filter_id;
OrbisNpWebApi2PushEventCallback cb_func;
void* user_arg;
bool is_busy;
};
class PushEventPushContext {
public:
PushEventPushContext(u32 new_timeout, UserContext* user_ctx)
: timeout(new_timeout), parent_user_ctx(user_ctx) {}
void Initialize();
OrbisNpWebApi2PushEventPushContextId* GetId() {
return &id;
}
// Returns the actual numerical id currently stored in id.
s64 GetFakeId() {
s64 fake_id{};
std::memcpy(&fake_id, &id, sizeof(s64));
return fake_id;
}
void Start() {
started = true;
}
void SetState(s32 new_state) {
state = new_state;
}
bool CallbackRunning() {
return state == 2;
}
private:
s32 state{};
u32 timeout{};
UserContext* parent_user_ctx{};
u64 end_time{};
u64 start_time{};
OrbisNpWebApi2PushEventPushContextId id{};
bool started{};
};
struct PushEventPushContextCallback {
s32 id;
s32 filter_id;
OrbisNpWebApi2PushEventPushContextCallback cb_func;
void* user_arg;
bool is_busy;
};
}; // namespace Libraries::Np::NpWebApi2

View File

@ -14,6 +14,7 @@
#include "core/libraries/np/np_handler.h"
#include "imgui/imgui_layer.h"
#include "imgui/invitation_prompt_layer.h"
#include "imgui/renderer/imgui_core.h"
#include "imgui/shadnet_notifications_layer.h"
namespace ImGui::InvitationPrompt {
@ -33,11 +34,16 @@ struct Prompt {
State state = State::Waiting;
};
constexpr std::size_t kMaxPrompts = 4; // oldest dropped beyond this
constexpr std::size_t kMaxPrompts = 4; // oldest dropped beyond this
constexpr float kFocusHoldSeconds = 0.6f; // hold Share/Back this long to focus the prompt
std::mutex g_mutex;
std::deque<Prompt> g_prompts;
bool g_focused = false;
bool g_capture_held = false;
float g_back_held_secs = 0.0f;
class InvitationPromptUI final : public ImGui::Layer {
public:
void Draw() override;
@ -92,6 +98,20 @@ void RunDecline(Prompt prompt) {
}).detach();
}
void SetFocused(bool focused) {
if (g_focused == focused) {
return;
}
g_focused = focused;
if (focused && !g_capture_held) {
ImGui::Core::AcquireGamepadInputCapture();
g_capture_held = true;
} else if (!focused && g_capture_held) {
ImGui::Core::ReleaseGamepadInputCapture();
g_capture_held = false;
}
}
} // namespace
void Push(s32 user_id, std::string invitation_id, std::string session_id, std::string from_npid) {
@ -117,6 +137,7 @@ void Register() {
}
void Unregister() {
SetFocused(false);
ImGui::Layer::RemoveLayer(&g_layer);
}
@ -124,28 +145,49 @@ 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());
}
if (snapshot.empty()) {
SetFocused(false);
return;
}
if (!g_focused) {
if (ImGui::IsKeyDown(ImGuiKey_GamepadBack)) {
g_back_held_secs += ImGui::GetIO().DeltaTime;
if (g_back_held_secs >= kFocusHoldSeconds) {
g_back_held_secs = 0.0f;
SetFocused(true);
}
} else {
g_back_held_secs = 0.0f;
}
} else {
// Stage 2 -> 1: Circle (nav cancel) or Escape returns control to the game.
if (ImGui::IsKeyPressed(ImGuiKey_GamepadFaceRight, false) ||
ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
SetFocused(false);
}
}
auto& io = ImGui::GetIO();
const float scale = io.DisplaySize.x / 1920.0f;
const float width = 420.0f * scale;
const float width = 440.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);
ImGui::SetNextWindowBgAlpha(g_focused ? 0.97f : 0.85f);
const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_AlwaysAutoResize;
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize;
if (g_focused) {
ImGui::SetNextWindowFocus();
} else {
flags |= ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing;
}
if (ImGui::Begin("##invitation_prompts", nullptr, flags)) {
bool first = true;
@ -167,6 +209,9 @@ void InvitationPromptUI::Draw() {
if (ImGui::Button(busy ? "Joining..." : "Accept", ImVec2(120.0f * scale, 0.0f))) {
RunAccept(p);
}
if (g_focused && &p == snapshot.data()) {
ImGui::SetItemDefaultFocus(); // nav lands on Accept of the newest-focused prompt
}
ImGui::SameLine();
if (ImGui::Button("Decline", ImVec2(120.0f * scale, 0.0f))) {
RunDecline(p);
@ -174,6 +219,10 @@ void InvitationPromptUI::Draw() {
ImGui::PopID();
ImGui::EndDisabled();
}
ImGui::Spacing();
ImGui::TextDisabled(g_focused ? "D-Pad: select Cross: confirm Circle/Esc: back to game"
: "Hold Share/Back to respond, or click");
}
ImGui::End();
}

View File

@ -137,6 +137,9 @@ u32 ShadNetClient::GetAddrLocal() const {
u32 ShadNetClient::GetAddrServer() const {
return m_addr_server.load();
}
bool ShadNetClient::IsMatching2Enabled() const {
return m_matching2_enabled.load();
}
u32 ShadNetClient::GetNumFriends() const {
std::lock_guard lock(m_mutex_friends);
@ -518,6 +521,19 @@ u64 ShadNetClient::SetAppearOffline(bool enable) {
return SubmitRequest(CommandType::SetAppearOffline, MakeProtoPayload(req));
}
bool ShadNetClient::RequestServerFeatures() {
const u64 pkt_id = m_pkt_counter.fetch_add(1);
std::vector<u8> empty_payload;
std::vector<u8> pkt = BuildPacket(CommandType::GetServerFeatures, pkt_id, empty_payload);
{
std::lock_guard lock(m_mutex_send_queue);
m_send_queue.push_back(std::move(pkt));
}
m_cv_send_queue.notify_one();
LOG_DEBUG(ShadNet, "GetServerFeatures request fired pkt_id={}", pkt_id);
return true;
}
// Packet dispatch
void ShadNetClient::DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id,
@ -531,6 +547,9 @@ void ShadNetClient::DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id,
case CommandType::GetToken:
HandleGetTokenReply(payload);
break;
case CommandType::GetServerFeatures:
HandleServerFeaturesReply(payload);
break;
default:
if (onAsyncReply) {
// Every reply body starts with an ErrorType byte.
@ -648,21 +667,21 @@ void ShadNetClient::HandleLoginReply(const std::vector<u8>& payload) {
void ShadNetClient::HandleGetTokenReply(const std::vector<u8>& payload) {
if (payload.empty()) {
LOG_ERROR(ShadNet, "Empty GetToken reply");
m_sem_authenticated.release();
RequestServerFeatures();
return;
}
const ErrorType err = static_cast<ErrorType>(payload[0]);
if (err != ErrorType::NoError) {
LOG_WARNING(ShadNet, "GetToken returned error={} — WebAPI calls will be unauthenticated",
static_cast<int>(err));
m_sem_authenticated.release();
RequestServerFeatures();
return;
}
shadnet::GetTokenReply pb;
const std::string blob = ExtractBlob(payload, 1);
if (blob.empty() || !pb.ParseFromString(blob)) {
LOG_ERROR(ShadNet, "Failed to parse GetTokenReply proto");
m_sem_authenticated.release();
RequestServerFeatures();
return;
}
{
@ -671,6 +690,33 @@ void ShadNetClient::HandleGetTokenReply(const std::vector<u8>& payload) {
}
LOG_INFO(ShadNet, "Bearer token captured ({} chars) for accountID={} canonical npid='{}'",
pb.token().size(), pb.user_id(), pb.npid());
RequestServerFeatures();
}
void ShadNetClient::HandleServerFeaturesReply(const std::vector<u8>& payload) {
bool matching2_enabled = false;
bool parsed = false;
if (!payload.empty()) {
const ErrorType err = static_cast<ErrorType>(payload[0]);
if (err == ErrorType::NoError) {
shadnet::ServerFeaturesReply pb;
const std::string blob = ExtractBlob(payload, 1);
if (!blob.empty() && pb.ParseFromString(blob)) {
matching2_enabled = pb.matching2_enabled();
parsed = true;
}
} else {
LOG_WARNING(ShadNet,
"GetServerFeatures returned error={} - assuming Matching2 disabled",
static_cast<int>(err));
}
}
m_matching2_enabled.store(matching2_enabled);
m_server_features_received.store(parsed);
LOG_INFO(ShadNet, "Server features: matching2_enabled={}{}",
matching2_enabled ? "true" : "false", parsed ? "" : " (defaulted)");
m_sem_authenticated.release();
}
@ -795,6 +841,28 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
onRoomEvent(n);
break;
}
case NotificationType::RoomMessage: {
shadnet::NotifyRoomMessage pb;
if (!pb.ParseFromString(blob)) {
break;
}
NotifyRoomMessage n;
n.ctx_id = pb.ctx_id();
n.room_id = pb.room_id();
n.src_member_id = pb.src_member_id();
n.event = pb.event();
n.cast_type = pb.cast_type();
for (const u32 member_id : pb.dst_member_ids()) {
n.dst_member_ids.push_back(member_id);
}
n.src_npid = pb.src_npid();
n.src_account_id = pb.src_account_id();
n.src_platform = pb.src_platform();
n.msg.assign(pb.msg().begin(), pb.msg().end());
if (onRoomMessage)
onRoomMessage(n);
break;
}
case NotificationType::WebApiPushEvent: {
// Length-prefixed fields: npServiceName, npServiceLabel(u32 LE), dataType,
// data, fromNpid, toNpid. ExtractBlob returns {} past the end, so a short

View File

@ -66,22 +66,7 @@ enum class CommandType : u16 {
RemoveFriend = 9,
AddBlock = 10,
RemoveBlock = 11,
// Matchmaking
ContextStart = 12,
CreateRoom = 13,
JoinRoom = 14,
LeaveRoom = 15,
SearchRoom = 16,
RequestSignalingInfos = 17,
ContextStop = 18,
SetUserInfo = 19,
SetRoomDataInternal = 20,
SetRoomDataExternal = 21,
KickoutRoomMember = 22,
GetWorldInfoList = 23,
GetRoomDataExternalList = 24,
GetUserInfoList = 25,
GetRoomMemberDataExternalList = 26,
GetServerFeatures = 12,
GetBoardInfos = 30,
RecordScore = 31,
RecordScoreData = 32,
@ -93,6 +78,23 @@ enum class CommandType : u16 {
GetScoreGameDataByAccId = 38,
GetToken = 39,
SetAppearOffline = 40,
// Matchmaking
ContextStart = 100,
CreateRoom = 101,
JoinRoom = 102,
LeaveRoom = 103,
SearchRoom = 104,
RequestSignalingInfos = 105,
ContextStop = 106,
SetUserInfo = 107,
SetRoomDataInternal = 108,
SetRoomDataExternal = 109,
KickoutRoomMember = 110,
GetWorldInfoList = 111,
GetRoomDataExternalList = 112,
GetUserInfoList = 113,
GetRoomMemberDataExternalList = 114,
SendRoomMessage = 115,
};
enum class NotificationType : u16 {
@ -101,6 +103,7 @@ enum class NotificationType : u16 {
FriendLost = 7,
FriendStatus = 8,
RoomEvent = 10,
RoomMessage = 11,
WebApiPushEvent = 17, // Generic NP WebApi push event
};
@ -234,6 +237,19 @@ struct NotifyRoomEvent {
std::vector<MatchingBinAttr> bin_attrs;
};
struct NotifyRoomMessage {
u32 ctx_id = 0;
u64 room_id = 0;
u32 src_member_id = 0;
u32 event = 0;
u32 cast_type = 0;
std::vector<u32> dst_member_ids;
std::string src_npid;
u64 src_account_id = 0;
u32 src_platform = 0;
std::vector<u8> msg;
};
// ShadNetClient
class ShadNetClient {
@ -258,6 +274,7 @@ public:
u64 GetUserId() const;
u32 GetAddrLocal() const;
u32 GetAddrServer() const;
bool IsMatching2Enabled() const;
u32 GetNumFriends() const;
std::optional<std::string> GetFriendNpid(u32 index) const;
@ -270,6 +287,7 @@ public:
std::function<void(const NotifyFriendLost&)> onFriendLost;
std::function<void(const NotifyFriendStatus&)> onFriendStatus;
std::function<void(const NotifyRoomEvent&)> onRoomEvent;
std::function<void(const NotifyRoomMessage&)> onRoomMessage;
std::function<void(const NotifyWebApiPushEvent&)> onWebApiPushEvent;
// Async reply callback.
// cmd —command this reply is for (matches the request's cmd)
@ -304,7 +322,9 @@ private:
void DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id, const std::vector<u8>& payload);
void HandleLoginReply(const std::vector<u8>& payload);
void HandleGetTokenReply(const std::vector<u8>& payload);
void HandleServerFeaturesReply(const std::vector<u8>& payload);
void HandleNotification(u16 cmd_raw, const std::vector<u8>& payload);
bool RequestServerFeatures();
// Helper: read a u32-LE-prefixed proto blob from a byte vector at pos.
static std::string ExtractBlob(const std::vector<u8>& p, int pos);
@ -349,6 +369,8 @@ private:
std::string m_bearer_token;
std::atomic<u32> m_addr_local{0};
std::atomic<u32> m_addr_server{0};
std::atomic<bool> m_matching2_enabled{false};
std::atomic<bool> m_server_features_received{false};
mutable std::mutex m_mutex_friends;
std::vector<FriendEntry> m_friends;

View File

@ -29,6 +29,10 @@ message SetAppearOfflineRequest {
bool appear_offline = 1;
}
message ServerFeaturesReply {
bool matching2_enabled = 1;
}
message FriendEntry {
string npid = 1;
bool online = 2;
@ -514,6 +518,16 @@ message SetRoomDataInternalReply { uint64 room_id = 1; }
message SetRoomDataExternalReply { uint64 room_id = 1; }
message KickoutRoomMemberReply { uint64 room_id = 1; }
message SendRoomMessageRequest {
uint64 room_id = 1;
uint32 cast_type = 2;
repeated uint32 dst_member_ids = 3;
bytes msg = 4;
int32 option = 5;
}
message SendRoomMessageReply { uint64 room_id = 1; }
// Notification payloads
// Every notification carries ctx_id so the client can route it to the owning
// context (the server tracks ctx_id per connection from ContextStartRequest).
@ -534,3 +548,18 @@ message NotifyRoomEvent {
bool has_passwd_mask = 9;
uint64 passwd_slot_mask = 10;
}
// NotificationType::RoomMessage (11) maps onto
// OrbisNpMatching2RoomMessageCallback(ctxId, roomId, srcMemberId, event, data).
message NotifyRoomMessage {
uint32 ctx_id = 1;
uint64 room_id = 2;
uint32 src_member_id = 3;
uint32 event = 4;
uint32 cast_type = 5;
repeated uint32 dst_member_ids = 6;
string src_npid = 7;
uint64 src_account_id = 8;
uint32 src_platform = 9;
bytes msg = 10;
}

View File

@ -691,6 +691,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
}
case PM4ItOpcode::EventWriteEos: {
const auto* event_eos = reinterpret_cast<const PM4CmdEventWriteEos*>(header);
if (rasterizer) {
rasterizer->ProcessDownloadImages();
}
event_eos->SignalFence([](void* address, u64 data, u32 num_bytes) {
auto* memory = Core::Memory::Instance();
if (!memory->TryWriteBacking(address, &data, num_bytes)) {
@ -709,6 +712,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
}
case PM4ItOpcode::EventWriteEop: {
const auto* event_eop = reinterpret_cast<const PM4CmdEventWriteEop*>(header);
if (rasterizer) {
rasterizer->ProcessDownloadImages();
}
event_eop->SignalFence(
[](void* address, u64 data, u32 num_bytes) {
auto* memory = Core::Memory::Instance();
@ -1137,6 +1143,9 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
}
case PM4ItOpcode::ReleaseMem: {
const auto* release_mem = reinterpret_cast<const PM4CmdReleaseMem*>(header);
if (rasterizer) {
rasterizer->ProcessDownloadImages();
}
release_mem->SignalFence(
[pipe_id = queue.pipe_id] {
Platform::IrqC::Instance()->Signal(static_cast<Platform::InterruptId>(pipe_id));

View File

@ -1068,6 +1068,10 @@ bool Rasterizer::ReadMemory(VAddr addr, u64 size) {
return true;
}
void Rasterizer::ProcessDownloadImages() {
texture_cache.ProcessDownloadImages();
}
bool Rasterizer::IsMapped(VAddr addr, u64 size) {
if (size == 0) {
// There is no memory, so not mapped.

View File

@ -60,6 +60,7 @@ public:
u32 ReadDataFromGds(u32 gsd_offset);
bool InvalidateMemory(VAddr addr, u64 size);
bool ReadMemory(VAddr addr, u64 size);
void ProcessDownloadImages();
bool IsMapped(VAddr addr, u64 size);
void MapMemory(VAddr addr, u64 size);
void UnmapMemory(VAddr addr, u64 size);

View File

@ -87,18 +87,18 @@ ImageId TextureCache::GetNullImage(const vk::Format format) {
void TextureCache::ProcessDownloadImages() {
for (const ImageId image_id : download_images) {
DownloadImageMemory(image_id);
DownloadImageMemory(image_id, true);
}
download_images.clear();
}
void TextureCache::DownloadImageMemory(ImageId image_id) {
void TextureCache::DownloadImageMemory(ImageId image_id, bool sync) {
Image& image = slot_images[image_id];
if (False(image.flags & ImageFlagBits::GpuModified)) {
return;
}
auto& download_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::Download);
const u32 download_size = image.info.pitch * image.info.size.height *
const u32 download_size = image.info.pitch * image.info.size.height * image.info.size.depth *
image.info.resources.layers * (image.info.num_bits / 8);
ASSERT(download_size <= image.info.guest_size);
const auto [download, offset] = download_buffer.Map(download_size);
@ -116,7 +116,7 @@ void TextureCache::DownloadImageMemory(ImageId image_id) {
.layerCount = image.info.resources.layers,
},
.imageOffset = {0, 0, 0},
.imageExtent = {image.info.size.width, image.info.size.height, 1},
.imageExtent = {image.info.size.width, image.info.size.height, image.info.size.depth},
};
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
@ -124,11 +124,17 @@ void TextureCache::DownloadImageMemory(ImageId image_id) {
cmdbuf.copyImageToBuffer(image.GetImage(), vk::ImageLayout::eTransferSrcOptimal,
download_buffer.Handle(), image_download);
scheduler.DeferPriorityOperation(
[this, device_addr = image.info.guest_address, download, download_size] {
Core::Memory::Instance()->TryWriteBacking(std::bit_cast<u8*>(device_addr), download,
download_size);
});
if (sync) {
scheduler.Finish();
Core::Memory::Instance()->TryWriteBacking(std::bit_cast<u8*>(image.info.guest_address),
download, download_size);
} else {
scheduler.DeferPriorityOperation(
[this, device_addr = image.info.guest_address, download, download_size] {
Core::Memory::Instance()->TryWriteBacking(std::bit_cast<u8*>(device_addr), download,
download_size);
});
}
}
void TextureCache::MarkAsMaybeDirty(ImageId image_id, Image& image) {

View File

@ -272,7 +272,7 @@ private:
ImageId GetNullImage(vk::Format format);
/// Copies image memory back to CPU.
void DownloadImageMemory(ImageId image_id);
void DownloadImageMemory(ImageId image_id, bool sync = false);
/// Thread function for copying downloaded images out to CPU memory.
void DownloadedImagesThread(const std::stop_token& token);