sceNpWebApi2SendRequest

This commit is contained in:
Stephen Miller 2026-06-29 00:02:11 -05:00
parent 2fd87eac6d
commit 248781ce5f
8 changed files with 353 additions and 28 deletions

View File

@ -18,7 +18,8 @@ s32 PS4_SYSV_ABI sceHttp2AddCookie() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader() {
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
const char* value, u32 mode) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
@ -48,12 +49,13 @@ s32 PS4_SYSV_ABI sceHttp2CreateCookieBox() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL() {
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
u64 content_length) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, char* user_agent, s32 http_ver,
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");
return ORBIS_OK;
@ -74,7 +76,7 @@ s32 PS4_SYSV_ABI sceHttp2DeleteTemplate() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders() {
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
@ -114,7 +116,7 @@ s32 PS4_SYSV_ABI sceHttp2GetResponseContentLength() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetStatusCode() {
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 request_id, s32* status_code) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
@ -145,7 +147,7 @@ s32 PS4_SYSV_ABI sceHttp2RemoveRequestHeader() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SendRequest() {
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
@ -236,7 +238,7 @@ s32 PS4_SYSV_ABI sceHttp2SetRedirectCallback() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength() {
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}

View File

@ -29,19 +29,21 @@ using OrbisHttp2PreSendCallback = PS4_SYSV_ABI s32 (*)(s32 request_id, s32 ssl_i
s32 PS4_SYSV_ABI sceHttp2AbortRequest();
s32 PS4_SYSV_ABI sceHttp2AddCookie();
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader();
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
const char* value, u32 mode);
s32 PS4_SYSV_ABI sceHttp2AuthCacheFlush();
s32 PS4_SYSV_ABI sceHttp2CookieExport();
s32 PS4_SYSV_ABI sceHttp2CookieFlush();
s32 PS4_SYSV_ABI sceHttp2CookieImport();
s32 PS4_SYSV_ABI sceHttp2CreateCookieBox();
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL();
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, char* user_agent, s32 http_ver,
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 sceHttp2DeleteCookieBox();
s32 PS4_SYSV_ABI sceHttp2DeleteRequest();
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate();
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders();
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size);
s32 PS4_SYSV_ABI sceHttp2GetAuthEnabled();
s32 PS4_SYSV_ABI sceHttp2GetAutoRedirect();
s32 PS4_SYSV_ABI sceHttp2GetCookie();
@ -49,13 +51,13 @@ s32 PS4_SYSV_ABI sceHttp2GetCookieBox();
s32 PS4_SYSV_ABI sceHttp2GetCookieStats();
s32 PS4_SYSV_ABI sceHttp2GetMemoryPoolStats();
s32 PS4_SYSV_ABI sceHttp2GetResponseContentLength();
s32 PS4_SYSV_ABI sceHttp2GetStatusCode();
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 PS4_SYSV_ABI sceHttp2ReadDataAsync();
s32 PS4_SYSV_ABI sceHttp2RedirectCacheFlush();
s32 PS4_SYSV_ABI sceHttp2RemoveRequestHeader();
s32 PS4_SYSV_ABI sceHttp2SendRequest();
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size);
s32 PS4_SYSV_ABI sceHttp2SendRequestAsync();
s32 PS4_SYSV_ABI sceHttp2SetAuthEnabled();
s32 PS4_SYSV_ABI sceHttp2SetAuthInfoCallback();
@ -74,7 +76,7 @@ s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCa
void* user_arg);
s32 PS4_SYSV_ABI sceHttp2SetRecvTimeOut();
s32 PS4_SYSV_ABI sceHttp2SetRedirectCallback();
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength();
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length);
s32 PS4_SYSV_ABI sceHttp2SetResolveRetry();
s32 PS4_SYSV_ABI sceHttp2SetResolveTimeOut();
s32 PS4_SYSV_ABI sceHttp2SetSendTimeOut();

View File

@ -277,18 +277,24 @@ s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SendMultipartRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
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_INFO(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() {
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
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() {

View File

@ -42,5 +42,13 @@ struct OrbisNpWebApi2ContentParameter {
u8 reserved[16];
};
struct OrbisNpWebApi2ResponseInformationOption {
s32 http_status;
s32 reserved;
char* error_object;
u64 error_object_size;
u64 response_data_size;
};
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi2

View File

@ -5,6 +5,7 @@
#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 {
@ -144,7 +145,7 @@ Request* UserContext::GetRequest(s64 request_id) {
return request;
}
bool IsInternalHeader(const char* header_name) {
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); });
@ -177,4 +178,98 @@ s32 Request::AddHttpRequestHeader(const char* field_name, const char* field_valu
return ORBIS_OK;
}
s32 Request::CreateHttpRequest(s32 http_template_id, const char* url) {
const char* method = this->method.empty() ? "UNKNOWN" : this->method.data();
s32 http_request_id = Libraries::Http2::sceHttp2CreateRequestWithURL(http_template_id, method,
url, this->content_length);
if (http_request_id < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to create Http2 request, error = {:#x}", http_request_id);
return http_request_id;
}
this->http_request_id = http_request_id;
s32 result = Libraries::Http2::sceHttp2AddRequestHeader(http_request_id, "Content-Type",
this->content_type.data(), 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to add Content-Type request header, error = {:#x}",
result);
return result;
}
UserContext* user_ctx = this->parent_ctx->GetUserContext(this->id >> 0x20);
const std::string bearer = NpHandler::GetInstance().GetBearerToken(user_ctx->GetUserId());
if (!bearer.empty()) {
const std::string auth_value = "Bearer " + bearer;
result = Libraries::Http2::sceHttp2AddRequestHeader(http_request_id, "Authorization",
auth_value.data(), 0);
}
if (bearer.empty() || result < 0) {
LOG_WARNING(Lib_NpWebApi2, "Failed to add Authorization request header");
}
// This would go on to add several other PSN-specific headers.
// As shadNet does not appear to use them, the logic is skipped for now.
for (HttpRequestHeader* header : this->http_headers) {
s32 result = Libraries::Http2::sceHttp2AddRequestHeader(
http_request_id, header->field_name.data(), header->field_value.data(), 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2,
"Failed to add request header, name = {}, value = {}, error = {:#x}",
header->field_name.data(), header->field_value.data(), result);
user_ctx->RemoveUser();
return result;
}
}
user_ctx->RemoveUser();
return result;
}
s32 Request::SendHttpRequest(void* data, u64 data_size) {
s32 result = 0;
if (this->content_length != 0 && this->sent_data == 0) {
// Real library seems to do some calculations, some parts including IPC calls.
// Not entirely sure how accurate this is in practice.
result = Libraries::Http2::sceHttp2SetRequestContentLength(this->http_request_id,
this->content_length);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to set content length, error = {:#x}", result);
return result;
}
}
if (!data || data_size == 0 || this->content_length == 0) {
result = Libraries::Http2::sceHttp2SendRequest(this->http_request_id, nullptr, 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to send request, error = {:#x}", result);
return result;
}
}
u64 actual_size = std::min<u64>(data_size, this->content_length - this->sent_data);
if (this->content_length <= this->sent_data) {
// Nothing left to send
return result;
}
result = Libraries::Http2::sceHttp2SendRequest(this->http_request_id, data, actual_size);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to send request, error = {:#x}", result);
return result;
}
this->sent_data += actual_size;
return result;
}
s32 Request::GetAllHttpResponseHeaders() {
char* header = nullptr;
u64 size = 0;
s32 result =
Libraries::Http2::sceHttp2GetAllResponseHeaders(this->http_request_id, &header, &size);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to get response headers, error = {:#x}", result);
return result;
}
this->http_response_headers = header;
this->http_response_header_size = size;
return result;
}
}; // namespace Libraries::Np::NpWebApi2

View File

@ -1,6 +1,7 @@
// 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_push_event.h"
#include "core/libraries/system/userservice.h"
@ -137,6 +138,10 @@ public:
return user_id;
}
s32 GetHttpTemplateId() {
return http_template_id;
}
s32 Initialize();
s32 CreateRequest(const char* api_group, const char* path, const char* method,
@ -164,7 +169,8 @@ public:
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) {}
multipart_supported(multipart), content_type(content_parameter->content_type),
content_length(content_parameter->content_length) {}
void Lock() {
parent_ctx->Lock();
@ -190,23 +196,84 @@ public:
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;
}
s32 AddHttpRequestHeader(const char* field_name, const char* field_value);
s32 CreateHttpRequest(s32 http_template_id, const char* url);
s32 SendHttpRequest(void* data, u64 data_size);
s32 GetAllHttpResponseHeaders();
private:
s64 id{};
u64 content_length{};
char* http_response_headers{};
u64 http_response_header_size{};
s32 user_count{};
s32 http_request_id{};
s32 timeout{};
s32 sent_data{};
bool multipart_supported{};
bool sent{};
bool aborted{};
bool expired{};
u64 end_time{};
LibraryContext* parent_ctx{};
std::string api_group{};
std::string path{};
std::string method{};
std::string content_type{};
bool multipart_supported{};
bool sent{};
struct HttpRequestHeader {
std::string field_name;

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_web_api2/np_web_api2_context.h"
@ -185,4 +186,145 @@ s32 addHttpRequestHeader(s64 request_id, const char* field_name, const char* fie
return result;
}
static s32 checkRequestStatus(Request* request, UserContext* user_ctx, LibraryContext* lib_ctx) {
if (request->Expired()) {
LOG_ERROR(Lib_NpWebApi2, "Request timed out");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_TIMEOUT;
}
if (request->Aborted()) {
LOG_ERROR(Lib_NpWebApi2, "Request aborted");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_ABORTED;
}
return ORBIS_OK;
}
s32 sendRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetEndTime();
if (request->IsMultipart()) {
if (part_index == 0) {
LOG_ERROR(Lib_NpWebApi2, "part_index 0 is prohibited for multipart requests");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_FUNCTION_CALL;
}
// TODO: Multipart logic
}
if (!request->HasSent()) {
request->MarkSent();
}
request->Lock();
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();
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();
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;
}
}; // namespace Libraries::Np::NpWebApi2

View File

@ -1,6 +1,7 @@
// 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 {
@ -12,4 +13,6 @@ s32 createRequest(s32 user_ctx_id, const char* api_group, const char* path, cons
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
s64* request_id);
s32 addHttpRequestHeader(s64 request_id, const char* field_name, const char* field_value);
s32 sendRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option);
}; // namespace Libraries::Np::NpWebApi2