diff --git a/CMakeLists.txt b/CMakeLists.txt index 943969101..fc7ad361a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 4e61b87a2..830180dba 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -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" diff --git a/src/core/libraries/network/http2.cpp b/src/core/libraries/network/http2.cpp index d71c04aed..cdaac4f9a 100644 --- a/src/core/libraries/network/http2.cpp +++ b/src/core/libraries/network/http2.cpp @@ -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 + namespace Libraries::Http2 { -int PS4_SYSV_ABI _Z5dummyv() { +std::map 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); diff --git a/src/core/libraries/network/http2.h b/src/core/libraries/network/http2.h index 99359518a..8c30345cf 100644 --- a/src/core/libraries/network/http2.h +++ b/src/core/libraries/network/http2.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/network/net_upnp.cpp b/src/core/libraries/network/net_upnp.cpp index de54cc830..a014737b4 100644 --- a/src/core/libraries/network/net_upnp.cpp +++ b/src/core/libraries/network/net_upnp.cpp @@ -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; diff --git a/src/core/libraries/network/net_upnp.h b/src/core/libraries/network/net_upnp.h index ffc2c6c4c..6fdc92409 100644 --- a/src/core/libraries/network/net_upnp.h +++ b/src/core/libraries/network/net_upnp.h @@ -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 m_started{false}; + std::atomic m_p2p_features_enabled{true}; std::atomic m_done{false}; std::atomic m_available{false}; std::atomic m_external_ip_nbo{0}; diff --git a/src/core/libraries/np/np_common.cpp b/src/core/libraries/np/np_common.cpp index e95cf3322..59a258edd 100644 --- a/src/core/libraries/np/np_common.cpp +++ b/src/core/libraries/np/np_common.cpp @@ -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 diff --git a/src/core/libraries/np/np_common.h b/src/core/libraries/np/np_common.h index 7ff5a81c0..9f28d1649 100644 --- a/src/core/libraries/np/np_common.h +++ b/src/core/libraries/np/np_common.h @@ -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, diff --git a/src/core/libraries/np/np_error.h b/src/core/libraries/np/np_error.h index c2fe511d1..ccbe74c76 100644 --- a/src/core/libraries/np/np_error.h +++ b/src/core/libraries/np/np_error.h @@ -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; diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp index 9a9c048a7..ba218ae15 100644 --- a/src/core/libraries/np/np_handler.cpp +++ b/src/core/libraries/np/np_handler.cpp @@ -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& body) { const auto cmd_val = static_cast(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); diff --git a/src/core/libraries/np/np_matching2/np_matching2.cpp b/src/core/libraries/np/np_matching2/np_matching2.cpp index a5a2cfb2e..7e99f3cda 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2.cpp @@ -3,6 +3,7 @@ #include #include +#include #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(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(); + 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; } diff --git a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp index 54bf6c05f..34f20a244 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp @@ -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(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(m.account_id()); mc.platform = static_cast(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(m.member_id()); dst.teamId = static_cast(m.team_id()); dst.natType = static_cast(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(m.account_id()); dst.user.platform = static_cast(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(m.member_id()); dst.teamId = static_cast(m.team_id()); dst.natType = static_cast(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(); @@ -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(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(r.owner_account_id()); dst.owner.platform = static_cast(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(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(r.owner_account_id()); dst.owner.platform = static_cast(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(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(src.account_id()); dst.user.platform = static_cast(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(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(u.account_id()); dst.user.platform = static_cast(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& dstMembers, + const MemberCache* srcMember, const std::vector& msg) { + p.Reset(); + + p.room_message_dst = std::make_unique(); + *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(); + p.room_message_src_online_id = std::make_unique(); + *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(); + 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(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(); + *p.room_message_src_npid = {}; + if (srcMember) { + *p.room_message_src_npid = srcMember->np_id; + } + + p.room_message_info = std::make_unique(); + 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(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(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) { diff --git a/src/core/libraries/np/np_matching2/np_matching2_internal.h b/src/core/libraries/np/np_matching2/np_matching2_internal.h index fdfa8077a..3e25b9437 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.h +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.h @@ -132,6 +132,16 @@ struct CallbackPayload { std::vector ext_owner_npids; void* request_data = nullptr; + std::unique_ptr room_message_info; + std::unique_ptr room_message_info_a; + std::unique_ptr room_message_dst; + std::unique_ptr room_message_src_npid; + std::unique_ptr room_message_src_addr; + std::unique_ptr room_message_src_online_id; + std::vector room_message_multicast_members; + std::vector room_message_data; + void* room_message_callback_data = nullptr; + std::unique_ptr room_member_update; std::unique_ptr room_member_update_a; std::unique_ptr 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 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 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& dstMembers, + const MemberCache* srcMember, const std::vector& msg); void InitEventDispatcher(); void TermEventDispatcher(); diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp index e7d6c3f22..94882fde4 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -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 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 std::vector 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 request_payload_owner; if (error_code == 0) { + request_payload_owner = std::make_shared(); + 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(n.room_id); const auto member_id = static_cast(n.member_id); - CallbackPayload& p = ctx->room_event_payload; - p.Reset(); + auto payload_owner = std::make_shared(); + 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(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(n.member_account_id); mc.platform = static_cast(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(n.ctx_id)); + if (!ctx) { + return; + } + + const auto room_id = static_cast(n.room_id); + const auto src_member_id = static_cast(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(n.src_account_id); + fallback_src.platform = static_cast(n.src_platform); + src_member = &fallback_src; + } + + std::vector 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(member_id)); + } + + auto payload_owner = std::make_shared(); + void* data = BuildRoomMessagePayload(*payload_owner, ctx->a_variant, + static_cast(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 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(tcp_port + 1)); + g_mm.server_udp_port = + matching2_enabled ? Libraries::Net::sceNetHtons(static_cast(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 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(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(req_event), static_cast(cmd)); + + s32 error_code = ORBIS_NP_MATCHING2_ERROR_SERVER_NOT_AVAILABLE; + void* request_data = nullptr; + std::shared_ptr 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(); + 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(req_event), static_cast(cmd), a_variant, + reinterpret_cast(request_cb.callback), fmt::ptr(request_cb.arg)); + const u64 pkt_id = client->SubmitRequest(static_cast(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(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 client; { std::lock_guard lock(g_mm.mutex); diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.h b/src/core/libraries/np/np_matching2/np_matching2_mm.h index 685d120ba..b74707102 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_mm.h +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.h @@ -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 client, @@ -45,7 +46,7 @@ void OnMatchingReply(ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType er const std::vector& 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& 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, diff --git a/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp b/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp index 306b03ce8..ee8f33886 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #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(pkt.online_id_from), + ORBIS_NP_ONLINEID_MAX_LENGTH)); const auto kind = static_cast(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); diff --git a/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp b/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp index 651d357db..59a284942 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp @@ -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(); diff --git a/src/core/libraries/np/np_signaling/np_signaling_state.cpp b/src/core/libraries/np/np_signaling/np_signaling_state.cpp index 26cd9a2cd..948e602c4 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_state.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling_state.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #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(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{}; { diff --git a/src/core/libraries/np/np_signaling/np_signaling_stubs.cpp b/src/core/libraries/np/np_signaling/np_signaling_stubs.cpp index 981b97ec6..bb2ce65f7 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_stubs.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling_stubs.cpp @@ -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; } diff --git a/src/core/libraries/np/np_signaling/np_signaling_stubs.h b/src/core/libraries/np/np_signaling/np_signaling_stubs.h index 63067abc6..a724b29af 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_stubs.h +++ b/src/core/libraries/np/np_signaling/np_signaling_stubs.h @@ -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(); diff --git a/src/core/libraries/np/np_types.h b/src/core/libraries/np/np_types.h index 26ad1316a..6fe5675f4 100644 --- a/src/core/libraries/np/np_types.h +++ b/src/core/libraries/np/np_types.h @@ -3,6 +3,10 @@ #pragma once +#include +#include +#include + #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(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 \ No newline at end of file +}; // namespace Libraries::Np diff --git a/src/core/libraries/np/np_types2.h b/src/core/libraries/np/np_types2.h index 314da4573..cece77d3d 100644 --- a/src/core/libraries/np/np_types2.h +++ b/src/core/libraries/np/np_types2.h @@ -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; diff --git a/src/core/libraries/np/np_web_api2.cpp b/src/core/libraries/np/np_web_api2.cpp deleted file mode 100644 index e7489ccba..000000000 --- a/src/core/libraries/np/np_web_api2.cpp +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2.h b/src/core/libraries/np/np_web_api2.h deleted file mode 100644 index 1e970d08e..000000000 --- a/src/core/libraries/np/np_web_api2.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2.cpp b/src/core/libraries/np/np_web_api2/np_web_api2.cpp new file mode 100644 index 000000000..7bcbdf040 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2.cpp @@ -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(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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2.h b/src/core/libraries/np/np_web_api2/np_web_api2.h new file mode 100644 index 000000000..01816bcc4 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2_context.cpp b/src/core/libraries/np/np_web_api2/np_web_api2_context.cpp new file mode 100644 index 000000000..1424ff054 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2_context.cpp @@ -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(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(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(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(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(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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2_context.h b/src/core/libraries/np/np_web_api2/np_web_api2_context.h new file mode 100644 index 000000000..17055d597 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2_context.h @@ -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 +#include +#include +#include +#include + +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(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(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 user_contexts{}; + std::map push_event_handles{}; + std::map 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 requests{}; + std::map push_event_callbacks{}; + std::map push_contexts{}; + std::map 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 http_headers{}; +}; + +}; // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2_internal.cpp b/src/core/libraries/np/np_web_api2/np_web_api2_internal.cpp new file mode 100644 index 000000000..eacdd1251 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2_internal.cpp @@ -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 +#include + +namespace Libraries::Np::NpWebApi2 { + +std::recursive_mutex g_mutex{}; +s32 g_current_lib_context_id{}; +std::map 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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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(request_id >> 0x30); + s32 user_ctx_id = static_cast(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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2_internal.h b/src/core/libraries/np/np_web_api2/np_web_api2_internal.h new file mode 100644 index 000000000..53d75cd4b --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2_internal.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2_push_event.cpp b/src/core/libraries/np/np_web_api2/np_web_api2_push_event.cpp new file mode 100644 index 000000000..ece4023c0 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2_push_event.cpp @@ -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> 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 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 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2/np_web_api2_push_event.h b/src/core/libraries/np/np_web_api2/np_web_api2_push_event.h new file mode 100644 index 000000000..b4270cda7 --- /dev/null +++ b/src/core/libraries/np/np_web_api2/np_web_api2_push_event.h @@ -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 filter_params{}; + std::vector> 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 \ No newline at end of file diff --git a/src/imgui/invitation_prompt_layer.cpp b/src/imgui/invitation_prompt_layer.cpp index 4553a7481..228dc6b4c 100644 --- a/src/imgui/invitation_prompt_layer.cpp +++ b/src/imgui/invitation_prompt_layer.cpp @@ -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 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 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(); } diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp index 5a73af63d..4075f9a30 100644 --- a/src/shadnet/client.cpp +++ b/src/shadnet/client.cpp @@ -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 empty_payload; + std::vector 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& payload) { void ShadNetClient::HandleGetTokenReply(const std::vector& payload) { if (payload.empty()) { LOG_ERROR(ShadNet, "Empty GetToken reply"); - m_sem_authenticated.release(); + RequestServerFeatures(); return; } const ErrorType err = static_cast(payload[0]); if (err != ErrorType::NoError) { LOG_WARNING(ShadNet, "GetToken returned error={} — WebAPI calls will be unauthenticated", static_cast(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& 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& payload) { + bool matching2_enabled = false; + bool parsed = false; + + if (!payload.empty()) { + const ErrorType err = static_cast(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(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& 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 diff --git a/src/shadnet/client.h b/src/shadnet/client.h index 8d537ea76..fc438ce66 100644 --- a/src/shadnet/client.h +++ b/src/shadnet/client.h @@ -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 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 dst_member_ids; + std::string src_npid; + u64 src_account_id = 0; + u32 src_platform = 0; + std::vector 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 GetFriendNpid(u32 index) const; @@ -270,6 +287,7 @@ public: std::function onFriendLost; std::function onFriendStatus; std::function onRoomEvent; + std::function onRoomMessage; std::function 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& payload); void HandleLoginReply(const std::vector& payload); void HandleGetTokenReply(const std::vector& payload); + void HandleServerFeaturesReply(const std::vector& payload); void HandleNotification(u16 cmd_raw, const std::vector& payload); + bool RequestServerFeatures(); // Helper: read a u32-LE-prefixed proto blob from a byte vector at pos. static std::string ExtractBlob(const std::vector& p, int pos); @@ -349,6 +369,8 @@ private: std::string m_bearer_token; std::atomic m_addr_local{0}; std::atomic m_addr_server{0}; + std::atomic m_matching2_enabled{false}; + std::atomic m_server_features_received{false}; mutable std::mutex m_mutex_friends; std::vector m_friends; diff --git a/src/shadnet/shadnet.proto b/src/shadnet/shadnet.proto index 5ee0621ba..48725fd07 100644 --- a/src/shadnet/shadnet.proto +++ b/src/shadnet/shadnet.proto @@ -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; +} diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 0648df922..f6b5958e1 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -691,6 +691,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(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 dcb, std::span(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 acb, u32 vqid) { } case PM4ItOpcode::ReleaseMem: { const auto* release_mem = reinterpret_cast(header); + if (rasterizer) { + rasterizer->ProcessDownloadImages(); + } release_mem->SignalFence( [pipe_id = queue.pipe_id] { Platform::IrqC::Instance()->Signal(static_cast(pipe_id)); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6f89bd393..7efde2c5b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -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. diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 1820da417..416588336 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -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); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 7c94a0176..7cb1539af 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -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(device_addr), download, - download_size); - }); + if (sync) { + scheduler.Finish(); + Core::Memory::Instance()->TryWriteBacking(std::bit_cast(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(device_addr), download, + download_size); + }); + } } void TextureCache::MarkAsMaybeDirty(ImageId image_id, Image& image) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 34a8472c4..7ad29bcc6 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -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);