Merge pull request #22932 from hrydgard/http-ids-npauth-ticket

sceHttp: Stable ids; NpAuth: Return the ticket param asked for
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-11-06 14:11:19 +01:00
commit b69424e8f2
5 files changed
+242 -157

No files matched your search

+123 -119
View File
@@ -35,7 +35,53 @@
#include "Common/Net/HTTPClient.h"
static std::vector<std::shared_ptr<HTTPTemplate>> httpObjects;
// Templates, connections and requests are numbered separately, with the kind in the top byte
// (1, 0x01000001, 0x02000001). A new object takes the lowest free number (tests/net/http/ids).
enum HttpIdKind {
HTTP_ID_TEMPLATE = 0,
HTTP_ID_CONNECTION = 1,
HTTP_ID_REQUEST = 2,
HTTP_ID_KIND_COUNT,
};
static std::vector<std::shared_ptr<HTTPTemplate>> httpObjects[HTTP_ID_KIND_COUNT];
static HTTPTemplate *GetHttpObject(int id) {
const u32 kind = (u32)id >> 24;
const u32 number = (u32)id & 0x00FFFFFF;
if (kind >= HTTP_ID_KIND_COUNT || number == 0 || number > httpObjects[kind].size())
return nullptr;
return httpObjects[kind][number - 1].get();
}
static bool IsHttpId(int id, HttpIdKind kind) {
return ((u32)id >> 24) == (u32)kind && GetHttpObject(id) != nullptr;
}
static HTTPRequest *GetHttpRequest(int id) {
return IsHttpId(id, HTTP_ID_REQUEST) ? static_cast<HTTPRequest *>(GetHttpObject(id)) : nullptr;
}
static int AddHttpObject(HttpIdKind kind, std::shared_ptr<HTTPTemplate> obj) {
auto &objects = httpObjects[kind];
size_t index = 0;
while (index < objects.size() && objects[index])
index++;
if (index == objects.size())
objects.push_back(obj);
else
objects[index] = obj;
return ((int)kind << 24) | (int)(index + 1);
}
static void RemoveHttpObject(int id) {
httpObjects[(u32)id >> 24][((u32)id & 0x00FFFFFF) - 1].reset();
}
static void ClearHttpObjects() {
for (auto &objects : httpObjects)
objects.clear();
}
static std::mutex httpLock;
bool httpInited = false;
@@ -65,13 +111,14 @@ int HTTPTemplate::addRequestHeader(const char* name, const char* value, u32 mode
}
int HTTPTemplate::removeRequestHeader(const char* name) {
requestHeaders_.erase(name);
if (requestHeaders_.erase(name) == 0)
return SCE_HTTP_ERROR_NOT_FOUND;
return 0;
}
HTTPConnection::HTTPConnection(int templateID, const char* hostString, const char* scheme, u32 port, int enableKeepalive) {
// Copy base data as initial base value for this
HTTPTemplate::operator=(*httpObjects[templateID - 1LL]);
HTTPTemplate::operator=(*GetHttpObject(templateID));
// Initialize
this->templateID = templateID;
@@ -84,7 +131,7 @@ HTTPConnection::HTTPConnection(int templateID, const char* hostString, const cha
HTTPRequest::HTTPRequest(int connectionID, int method, const char *url, u64 contentLength, net::ResolveFunc customResolver) : client(customResolver) {
// Copy base data as initial base value for this
// Since dynamic_cast/dynamic_pointer_cast/typeid requires RTTI to be enabled (ie. /GR instead of /GR- on msvc, enabled by default on most compilers), so we can only use static_cast here
HTTPConnection::operator=(static_cast<HTTPConnection&>(*httpObjects[connectionID - 1LL]));
HTTPConnection::operator=(*static_cast<HTTPConnection *>(GetHttpObject(connectionID)));
// Initialize
this->connectionID = connectionID;
@@ -103,10 +150,6 @@ HTTPRequest::~HTTPRequest() {
}
int HTTPRequest::getResponseContentLength() {
// FIXME: Will sceHttpGetContentLength returns an error if the request was not sent yet?
//if (progress_.progress == 0.0f)
// return SCE_HTTP_ERROR_BEFORE_SEND;
entityLength_ = -1; // FIXME: should we default to 0 instead?
for (std::string& line : responseHeaders_) {
if (startsWithNoCase(line, "Content-Length")) {
@@ -129,17 +172,10 @@ int HTTPRequest::abortRequest() {
}
int HTTPRequest::getStatusCode() {
// FIXME: Will sceHttpGetStatusCode returns an error if the request was not sent yet?
//if (progress_.progress == 0.0f)
// return SCE_HTTP_ERROR_BEFORE_SEND;
return responseCode_;
}
int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) {
// FIXME: Will sceHttpGetAllHeader returns an error if the request was not sent yet?
//if (progress_.progress == 0.0f)
// return SCE_HTTP_ERROR_BEFORE_SEND;
const char* const delim = "\r\n";
std::ostringstream imploded;
std::copy(responseHeaders_.begin(), responseHeaders_.end(), std::ostream_iterator<std::string>(imploded, delim));
@@ -191,9 +227,6 @@ int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) {
}
int HTTPRequest::readData(u32 destDataPtr, u32 size) {
// FIXME: Will sceHttpReadData returns an error if the request was not sent yet?
//if (progress_.progress == 0.0f)
// return SCE_HTTP_ERROR_BEFORE_SEND;
u32 sz = std::min(size, (u32)responseContent_.size());
if (sz > 0) {
Memory::MemcpyUnchecked(destDataPtr, responseContent_.c_str(), sz);
@@ -289,6 +322,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) {
return SCE_HTTP_ERROR_ABORTED;
}
sent_ = true;
return 0;
}
@@ -301,23 +335,19 @@ void __HttpShutdown() {
httpsInited = false;
httpCacheInited = false;
for (const auto& it : httpObjects) {
if (it->className() == name_HTTPRequest)
for (const auto& it : httpObjects[HTTP_ID_REQUEST]) {
if (it)
(static_cast<HTTPRequest*>(it.get()))->abortRequest();
}
httpObjects.clear();
ClearHttpObjects();
}
// id: ID of the template or connection
// id: ID of a template
int sceHttpSetResolveRetry(int id, int retryCount) {
if (id <= 0 || id > (int)httpObjects.size())
if (!IsHttpId(id, HTTP_ID_TEMPLATE))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
const auto& conn = httpObjects[id - 1LL];
if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className());
conn->setResolveRetry(retryCount);
GetHttpObject(id)->setResolveRetry(retryCount);
return hleLogDebug(Log::sceNet, 0);
}
@@ -327,9 +357,7 @@ static int sceHttpInit(MIPSState *mips, int poolSize) {
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_ALREADY_INITED, "http already inited");
std::lock_guard<std::mutex> guard(httpLock);
httpObjects.clear();
// Reserve at least 1 element to prevent ::begin() from returning null when no element has been added yet
httpObjects.reserve(1);
ClearHttpObjects();
httpInited = true;
return hleLogDebug(Log::sceNet, 0);
}
@@ -337,7 +365,7 @@ static int sceHttpInit(MIPSState *mips, int poolSize) {
static int sceHttpEnd() {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpEnd()");
std::lock_guard<std::mutex> guard(httpLock);
httpObjects.clear();
ClearHttpObjects();
httpInited = false;
return hleLogDebug(Log::sceNet, 0);
}
@@ -369,13 +397,15 @@ static u32 sceHttpGetProxy(u32 id, u32 activateFlagPtr, u32 modePtr, u32 proxyHo
static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetStatusCode(%d, %x)", requestID, statusCodePtr);
if (requestID <= 0 || requestID > (int)httpObjects.size())
HTTPRequest *req = GetHttpRequest(requestID);
if (!req)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (!Memory::IsValidRange(statusCodePtr, 4))
return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE;
const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get();
if (!req->wasSent())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent");
// FIXME: According to JPCSP, try to connect the request first
//req->connect();
int status = req->getStatusCode();
@@ -389,13 +419,15 @@ static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) {
// Games will repeatedly called sceHttpReadData until it returns (the size read into the data buffer) 0
// FIXME: sceHttpReadData seems to be blocking current thread, since hleDelayResult can make Download progressbar to moves progressively instead of instantly jump to 100%
static int sceHttpReadData(int requestID, u32 dataPtr, u32 dataSize) {
if (requestID <= 0 || requestID > (int)httpObjects.size())
HTTPRequest *req = GetHttpRequest(requestID);
if (!req)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (!Memory::IsValidRange(dataPtr, dataSize))
return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE
const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get();
if (!req->wasSent())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent");
// FIXME: According to JPCSP, try to connect the request first
//req->connect();
@@ -420,13 +452,12 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) {
if (!httpInited)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_INIT, "http not initialized yet");
if (requestID <= 0 || requestID > (int)httpObjects.size())
HTTPRequest *req = GetHttpRequest(requestID);
if (!req)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (dataSize > 0 && !Memory::IsValidRange(dataPtr, dataSize))
return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE
const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get();
// Internally try to connect, and get response headers (at least the status code?)
int retval = req->sendRequest(dataPtr, dataSize);
return hleLogDebug(Log::sceNet, retval);
@@ -434,54 +465,53 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) {
static int sceHttpDeleteRequest(int requestID) {
std::lock_guard<std::mutex> guard(httpLock);
if (requestID <= 0 || requestID > (int)httpObjects.size())
if (!IsHttpId(requestID, HTTP_ID_REQUEST))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[requestID - 1LL]->className() != name_HTTPRequest)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
httpObjects.erase(httpObjects.begin() + requestID - 1);
RemoveHttpObject(requestID);
return hleLogDebug(Log::sceNet, 0);
}
// id: ID of the template, connection or request
static int sceHttpDeleteHeader(int id, const char *name) {
if (id <= 0 || id > (int)httpObjects.size())
HTTPTemplate *obj = GetHttpObject(id);
if (!obj)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (!name)
return hleLogError(Log::sceNet, -1, "invalid name"); // SCE_HTTP_ERROR_INVALID_VALUE
const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get();
return req->removeRequestHeader(name);
return obj->removeRequestHeader(name);
}
static int sceHttpDeleteConnection(int connectionID) {
std::lock_guard<std::mutex> guard(httpLock);
if (connectionID <= 0 || connectionID > (int)httpObjects.size())
if (!IsHttpId(connectionID, HTTP_ID_CONNECTION))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
for (const auto &it : httpObjects[HTTP_ID_REQUEST]) {
if (it && static_cast<HTTPRequest *>(it.get())->getConnectionID() == connectionID)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_CONNECTION_HAS_REQUESTS, "connection has requests");
}
httpObjects.erase(httpObjects.begin() + connectionID - 1);
RemoveHttpObject(connectionID);
return hleLogDebug(Log::sceNet, 0);
}
// id: ID of the template, connection or request
// id: ID of the template or connection
static int sceHttpSetConnectTimeOut(int id, u32 timeout) {
if (id <= 0 || id > (int)httpObjects.size())
if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
auto& conn = httpObjects[id - 1LL];
conn->setConnectTimeout(timeout);
GetHttpObject(id)->setConnectTimeout(timeout);
return hleLogDebug(Log::sceNet, 0);
}
// id: ID of the template, connection or request
// id: ID of the template or connection
static int sceHttpSetSendTimeOut(int id, u32 timeout) {
if (id <= 0 || id > (int)httpObjects.size())
if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
auto& conn = httpObjects[id - 1LL];
conn->setSendTimeout(timeout);
GetHttpObject(id)->setSendTimeout(timeout);
return hleLogDebug(Log::sceNet, 0);
}
@@ -533,17 +563,11 @@ static int sceHttpsDisableOption(int id) {
static int sceHttpCreateRequest(int connectionID, int method, const char *path, u64 contentLength) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequest(%d, %d, %s, %llx)", connectionID, method, safe_string(path), contentLength);
std::lock_guard<std::mutex> guard(httpLock);
if (connectionID <= 0 || connectionID > (int)httpObjects.size())
// Any method is accepted here.
if (!IsHttpId(connectionID, HTTP_ID_CONNECTION))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method");
httpObjects.emplace_back(std::make_shared<HTTPRequest>(connectionID, method, path ? path : "", contentLength, &ProcessHostnameWithInfraDNS));
int retid = (int)httpObjects.size();
int retid = AddHttpObject(HTTP_ID_REQUEST, std::make_shared<HTTPRequest>(connectionID, method, path ? path : "", contentLength, &ProcessHostnameWithInfraDNS));
return hleLogDebug(Log::sceNet, retid);
}
@@ -551,16 +575,12 @@ static int sceHttpCreateRequest(int connectionID, int method, const char *path,
static int sceHttpCreateConnection(int templateID, const char *hostString, const char *scheme, u32 port, int enableKeepalive) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnection(%d, %s, %s, %d, %d)", templateID, safe_string(hostString), safe_string(scheme), port, enableKeepalive);
std::lock_guard<std::mutex> guard(httpLock);
if (templateID <= 0 || templateID > (int)httpObjects.size())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate)
if (!IsHttpId(templateID, HTTP_ID_TEMPLATE))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
// TODO: Look up hostString in DNS here.
httpObjects.emplace_back(std::make_shared<HTTPConnection>(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive));
int retid = (int)httpObjects.size();
int retid = AddHttpObject(HTTP_ID_CONNECTION, std::make_shared<HTTPConnection>(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive));
return hleLogDebug(Log::sceNet, retid);
}
@@ -577,32 +597,31 @@ static int sceHttpGetNetworkErrno(int request, u32 errNumPtr) {
// id: ID of the template, connection or request
static int sceHttpAddExtraHeader(int id, const char *name, const char *value, int unknown) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpAddExtraHeader(%d, %s, %s, %d)", id, safe_string(name), safe_string(value), unknown);
if (id <= 0 || id > (int)httpObjects.size())
HTTPTemplate *obj = GetHttpObject(id);
if (!obj)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (!name || !value)
return hleLogError(Log::sceNet, -1, "invalid header"); // SCE_HTTP_ERROR_INVALID_VALUE
const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get();
return req->addRequestHeader(name, value, unknown);
return obj->addRequestHeader(name, value, unknown);
}
static int sceHttpAbortRequest(int requestID) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpAbortRequest(%d)", requestID);
if (requestID <= 0 || requestID > (int)httpObjects.size())
HTTPRequest *req = GetHttpRequest(requestID);
if (!req)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get();
return req->abortRequest();
}
static int sceHttpDeleteTemplate(int templateID) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpDeleteTemplate(%d)", templateID);
std::lock_guard<std::mutex> guard(httpLock);
if (templateID <= 0 || templateID > (int)httpObjects.size())
// Its connections stay usable.
if (!IsHttpId(templateID, HTTP_ID_TEMPLATE))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
httpObjects.erase(httpObjects.begin() + templateID - 1);
RemoveHttpObject(templateID);
return hleLogDebug(Log::sceNet, 0);
}
@@ -611,17 +630,13 @@ static int sceHttpSetMallocFunction(u32 mallocFuncPtr, u32 freeFuncPtr, u32 real
return 0;
}
// id: ID of the template or connection
// id: ID of a template
static int sceHttpSetResolveTimeOut(int id, u32 timeout) {
ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetResolveTimeOut(%d, %d)", id, timeout);
if (id <= 0 || id > (int)httpObjects.size())
if (!IsHttpId(id, HTTP_ID_TEMPLATE))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
const auto& conn = httpObjects[id - 1LL];
if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className());
conn->setResolveTimeout(timeout);
GetHttpObject(id)->setResolveTimeout(timeout);
return hleLogDebug(Log::sceNet, 0);
}
@@ -680,8 +695,7 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt
// Reporting to find more games to be tested
WARN_LOG_REPORT_ONCE(sceHttpCreateTemplate, Log::sceNet, "UNTESTED sceHttpCreateTemplate(%s, %d, %d)", safe_string(userAgent), httpVer, autoProxyConf);
std::lock_guard<std::mutex> guard(httpLock);
httpObjects.push_back(std::make_shared<HTTPTemplate>(userAgent? userAgent:"", httpVer, autoProxyConf));
int retid = (int)httpObjects.size();
int retid = AddHttpObject(HTTP_ID_TEMPLATE, std::make_shared<HTTPTemplate>(userAgent? userAgent:"", httpVer, autoProxyConf));
return hleLogDebug(Log::sceNet, retid);
}
@@ -689,53 +703,39 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt
static int sceHttpCreateRequestWithURL(int connectionID, int method, const char *url, u64 contentLength) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequestWithURL(%d, %d, %s, %llx)", connectionID, method, safe_string(url), contentLength);
std::lock_guard<std::mutex> guard(httpLock);
if (connectionID <= 0 || connectionID > (int)httpObjects.size())
// Any method is accepted here.
if (!IsHttpId(connectionID, HTTP_ID_CONNECTION))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method");
Url baseURL(url ? url : "");
if (!baseURL.Valid())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url");
httpObjects.emplace_back(std::make_shared<HTTPRequest>(connectionID, method, url ? url : "", contentLength, &ProcessHostnameWithInfraDNS));
int retid = (int)httpObjects.size();
int retid = AddHttpObject(HTTP_ID_REQUEST, std::make_shared<HTTPRequest>(connectionID, method, url ? url : "", contentLength, &ProcessHostnameWithInfraDNS));
return hleLogDebug(Log::sceNet, retid);
}
static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int enableKeepalive) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnectionWithURL(%d, %s, %d)", templateID, safe_string(url), enableKeepalive);
std::lock_guard<std::mutex> guard(httpLock);
if (templateID <= 0 || templateID > (int)httpObjects.size())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate)
if (!IsHttpId(templateID, HTTP_ID_TEMPLATE))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
Url baseURL(url ? url: "");
if (!baseURL.Valid())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url");
httpObjects.emplace_back(std::make_shared<HTTPConnection>(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive));
int retid = (int)httpObjects.size();
int retid = AddHttpObject(HTTP_ID_CONNECTION, std::make_shared<HTTPConnection>(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive));
return hleLogDebug(Log::sceNet, retid);
}
// id: ID of the template or connection
static int sceHttpSetRecvTimeOut(int id, u32 timeout) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpSetRecvTimeOut(%d, %d)", id, timeout);
if (id <= 0 || id > (int)httpObjects.size())
if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
const auto& conn = httpObjects[id - 1LL];
if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection))
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className());
conn->setRecvTimeout(timeout);
GetHttpObject(id)->setRecvTimeout(timeout);
return hleLogDebug(Log::sceNet, 0);
}
@@ -743,7 +743,8 @@ static int sceHttpSetRecvTimeOut(int id, u32 timeout) {
// Note: Megaman PoweredUp seems to have an invalid address stored at the headerAddrPtr location, may be the game expecting us (network library) to give them a valid header address?
static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizePtr) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetAllHeader(%d, %x, %x)", requestID, headerAddrPtr, headerSizePtr);
if (requestID <= 0 || requestID > (int)httpObjects.size())
HTTPRequest *req = GetHttpRequest(requestID);
if (!req)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (!Memory::IsValidRange(headerAddrPtr, 4))
@@ -752,7 +753,8 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP
if (!Memory::IsValidRange(headerSizePtr, 4))
return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE;
const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get();
if (!req->wasSent())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent");
// FIXME: According to JPCSP, try to connect the request first
//req->connect();
int retval = req->getAllResponseHeaders(headerAddrPtr, headerSizePtr);
@@ -762,13 +764,15 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP
// FIXME: contentLength is SceULong64 but this contentLengthPtr argument should be a 32bit pointer instead of 64bit, right?
static int sceHttpGetContentLength(int requestID, u32 contentLengthPtr) {
WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetContentLength(%d, %x)", requestID, contentLengthPtr);
if (requestID <= 0 || requestID > (int)httpObjects.size())
HTTPRequest *req = GetHttpRequest(requestID);
if (!req)
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id");
if (!Memory::IsValidRange(contentLengthPtr, 8))
return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE;
const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get();
if (!req->wasSent())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent");
// FIXME: According to JPCSP, try to connect the request first
//req->connect();
int len = req->getResponseContentLength();
+5
View File
@@ -52,6 +52,8 @@ enum SceHttpErrorCode {
SCE_HTTP_ERROR_BAD_RESPONSE = 0x80431064,
SCE_HTTP_ERROR_BEFORE_SEND = 0x80431065,
SCE_HTTP_ERROR_AFTER_SEND = 0x80431066,
// sceHttpDeleteConnection, while the connection has requests (tests/net/http/ids).
SCE_HTTP_ERROR_CONNECTION_HAS_REQUESTS = 0x80431067,
SCE_HTTP_ERROR_TIMEOUT = 0x80431068,
SCE_HTTP_ERROR_UNKOWN_AUTH_TYPE = 0x80431069,
SCE_HTTP_ERROR_INVALID_VERSION = 0x8043106A,
@@ -258,6 +260,7 @@ private:
u32 headerAddr_ = 0;
u32 headerSize_ = 0;
bool cancelled_ = false;
bool sent_ = false;
int responseCode_ = -1;
int entityLength_ = -1;
@@ -275,6 +278,8 @@ public:
void setInternalHeaderAddr(u32 addr) { headerAddr_ = addr; }
int getConnectionID() { return connectionID; }
// Until a send succeeds, the calls that read the response fail with SCE_HTTP_ERROR_BEFORE_SEND.
bool wasSent() const { return sent_; }
int getResponseRemainingContentLength() { return (int)responseContent_.size(); }
int getResponseContentLength();
+111 -37
View File
@@ -431,14 +431,13 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) {
return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_UNKNOWN, "Missing npOnlineId");
}
int result = length;
// Built here, then copied once we know it fits.
u8 staging[512]{};
// Built here and copied out, since the buffer can be shorter than the ticket.
u8 ticketBuf[0xF0 + sizeof(SceNpTicketHeader)]{};
SceNpTicket ticket = {};
// Dummy Login ticket returned as Login response. Dummy ticket contents were taken from https://www.psdevwiki.com/ps3/X-I-5-Ticket
ticket.header.version = TICKET_VER_2_1;
ticket.header.size = 0xF0; // size excluding the header
u8 *buf = staging + sizeof(ticket);
u8 *buf = ticketBuf + sizeof(ticket);
int ofs = 0;
ofs += writeTicketParam(buf, PARAM_TYPE_STRING_ASCII, "\x4c\x47\x56\x3b\x81\x39\x4a\x22\xd8\x6b\xc1\x57\x71\x6e\xfd\xb8\xab\x63\xcc\x51", 20); // 20 random letters, token key or SceNpSignature?
ofs += writeTicketU32Param(buf + ofs, PARAM_TYPE_INT, 0x0100); // a flags?
@@ -464,24 +463,23 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) {
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_NULL);
ticket.section.type = SECTION_TYPE_BODY;
ticket.section.size = ofs;
memcpy(staging, &ticket, sizeof(SceNpTicket));
memcpy(ticketBuf, &ticket, sizeof(SceNpTicket));
SceNpTicketSection footer = { SECTION_TYPE_FOOTER, 32 }; // footer section? ie. 32-bytes on version 2.1 containing 4-chars ASCII + 20-chars ASCII
memcpy(buf + ofs, &footer, sizeof(SceNpTicketSection));
ofs += sizeof(footer);
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x34\xcd\x3c\xa9", 4);
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x3a\x4b\x42\x66\x92\xda\x6b\x7c\xb7\x4c\xe8\xd9\x4f\x2b\x77\x15\x91\xb8\xa4\xa9", 20); // 20 random letters, token key or SceNpSignature?
// includes Language list? (36 zero bytes, staging is zeroed)
const u32 total = (u32)sizeof(ticket) + ofs + 36;
_dbg_assert_(total <= sizeof(staging));
// includes Language list? (36 zero bytes, left by the initializer)
_dbg_assert_(sizeof(ticket) + ofs + 36 == sizeof(ticketBuf));
// The whole ticket gets written, even if length is shorter.
if (!Memory::IsValidRange(bufferAddr, std::max(length, total))) {
// Like JPCSP's real ticket path, copy what fits.
if (!Memory::IsValidRange(bufferAddr, length)) {
return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_ARGUMENT, "invalid buffer");
}
Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket");
Memory::Memcpy(bufferAddr, staging, total, "NpAuthGetTicket");
Memory::Memcpy(bufferAddr, ticketBuf, std::min(length, (u32)sizeof(ticketBuf)), "NpAuthGetTicket");
result = ticket.header.size + sizeof(ticket.header); // dummy ticket is 248 bytes
const int result = (int)sizeof(ticketBuf); // dummy ticket is 248 bytes
return hleLogWarning(Log::sceNet, result);
}
@@ -524,43 +522,119 @@ int sceNpAuthDestroyRequest(int requestId)
return hleNoLog(0);
}
// Tickets are big-endian, and params needn't be aligned.
static u16 ReadTicketU16(u32 addr) {
return ((u16)Memory::ReadUnchecked_U8(addr) << 8) | Memory::ReadUnchecked_U8(addr + 1);
}
static u32 ReadTicketU32(u32 addr) {
return ((u32)ReadTicketU16(addr) << 16) | ReadTicketU16(addr + 2);
}
// Writes one param's value into the 256-byte output, as GetTicketParam does for every param it
// walks past. A NULL param leaves the output alone, any other clears it first, and a type with no
// value (unknown ones) leaves it clear. Numbers are read at their full size whatever length the
// param gives.
static void WriteTicketParamValue(u32 bufferPtr, u16 type, u32 valuePtr, u32 length) {
const u32 PARAM_BUFFER_MAX_SIZE = 256;
if (type == PARAM_TYPE_NULL) {
return;
}
Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE, "NpAuthGetTicketParam");
u32 readSize = 0;
switch (type) {
case PARAM_TYPE_INT: readSize = 4; break;
case PARAM_TYPE_LONG: case PARAM_TYPE_DATE: readSize = 8; break;
case PARAM_TYPE_STRING: case PARAM_TYPE_STRING_ASCII: readSize = std::min(length, PARAM_BUFFER_MAX_SIZE - 1); break;
case 0x3011: readSize = 4; break;
default: return;
}
if (!Memory::IsValidRange(valuePtr, readSize)) {
return;
}
switch (type) {
case PARAM_TYPE_INT:
Memory::WriteUnchecked_U32(ReadTicketU32(valuePtr), bufferPtr);
break;
case PARAM_TYPE_LONG:
case PARAM_TYPE_DATE:
Memory::WriteUnchecked_U64(((u64)ReadTicketU32(valuePtr) << 32) | ReadTicketU32(valuePtr + 4), bufferPtr);
break;
case 0x3011:
// A big-endian u16 followed by two bytes as they are.
Memory::WriteUnchecked_U16(ReadTicketU16(valuePtr), bufferPtr);
Memory::Memcpy(bufferPtr + 2, valuePtr + 2, 2, "NpAuthGetTicketParam");
break;
default:
// The terminator is left by the clear.
Memory::Memcpy(bufferPtr, valuePtr, readSize, "NpAuthGetTicketParam");
break;
}
}
// Behaviour from tests/net/npauth/ticketparam.
int sceNpAuthGetTicketParam(u32 ticketBufPtr, int ticketLen, int paramNum, u32 bufferPtr)
{
const u32 PARAM_BUFFER_MAX_SIZE = 256;
// The type each param has to have. Param 11 is never found.
static const u16 paramTypes[NUMBER_PARAMETERS - 1] = {
PARAM_TYPE_STRING_ASCII, PARAM_TYPE_INT, PARAM_TYPE_DATE, PARAM_TYPE_DATE, PARAM_TYPE_LONG, PARAM_TYPE_STRING,
PARAM_TYPE_STRING_ASCII, PARAM_TYPE_STRING, PARAM_TYPE_STRING_ASCII, PARAM_TYPE_INT, PARAM_TYPE_DATE,
};
if (!Memory::IsValidRange(bufferPtr, PARAM_BUFFER_MAX_SIZE)) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid buffer");
}
Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE); // JPCSP: This clear is always done, even when an error is returned
if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT);
// Cleared even when an error is returned.
Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE, "NpAuthGetTicketParam");
if (ticketLen < 4 || ticketLen > 0x10000) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_TICKET_SIZE, "invalid ticket size");
}
if (ticketLen < (int)sizeof(SceNpTicket) || !Memory::IsValidRange(ticketBufPtr, ticketLen)) {
if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid param");
}
if (!Memory::IsValidRange(ticketBufPtr, ticketLen)) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid ticket");
}
const SceNpTicket *ticket = (const SceNpTicket *)Memory::GetPointerUnchecked(ticketBufPtr);
const u32 ticketEnd = ticketBufPtr + ticketLen;
u32 inbuf = ticketBufPtr;
inbuf += sizeof(ticket->header);
inbuf += ticket->section.size + sizeof(ticket->section);
u32 outbuf = bufferPtr;
for (int i = 0; i < paramNum; i++) {
// Only copy params that are inside the ticket and fit in the output.
if (inbuf < ticketBufPtr || inbuf + sizeof(SceNpTicketParamData) > ticketEnd)
break;
const SceNpTicketParamData *ticketParam = (const SceNpTicketParamData *)Memory::GetPointerUnchecked(inbuf);
u32 sz = (u32)sizeof(SceNpTicketParamData) + ticketParam->length;
if (sz > ticketEnd - inbuf || sz > PARAM_BUFFER_MAX_SIZE - (outbuf - bufferPtr))
break;
Memory::MemcpyUnchecked(outbuf, inbuf, sz);
DEBUG_LOG(Log::sceNet, "%s - Param #%d: Type = %04x, Length = %u", __FUNCTION__, i, static_cast<unsigned int>(ticketParam->type), static_cast<unsigned int>(ticketParam->length));
outbuf += sz;
inbuf += sz;
if (outbuf - bufferPtr >= PARAM_BUFFER_MAX_SIZE || inbuf - ticketBufPtr >= (u32)ticketLen)
break;
// Major version in the top nibble, minor in the next byte: 2.1 and 3.0 are accepted.
const u32 version = ReadTicketU32(ticketBufPtr);
const u32 major = version >> 28;
const u32 minor = (version >> 16) & 0xFF;
if (!(major == 2 && minor == 1) && !(major == 3 && minor == 0)) {
return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_TICKET_VERSION, "unsupported ticket version %08x", version);
}
if (paramNum == NUMBER_PARAMETERS - 1) {
return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_TICKET_PARAM_NOT_FOUND);
}
// The header's size and the section's size aren't used.
if (ticketLen < (int)sizeof(SceNpTicket) || ReadTicketU16(ticketBufPtr + 8) != SECTION_TYPE_BODY) {
return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "no body section");
}
return hleLogWarning(Log::sceNet, 0);
// Every param up to the one asked for is written out in turn, so a failure leaves the last
// one written in the buffer.
const u32 ticketEnd = ticketBufPtr + (u32)ticketLen;
u32 inbuf = ticketBufPtr + (u32)sizeof(SceNpTicket);
for (int i = 0; i <= paramNum; i++) {
if (inbuf + sizeof(SceNpTicketParamData) > ticketEnd) {
return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "ticket ends before param %d", i);
}
const u16 type = ReadTicketU16(inbuf);
const u32 length = ReadTicketU16(inbuf + 2);
const u32 valuePtr = inbuf + (u32)sizeof(SceNpTicketParamData);
if (valuePtr + length > ticketEnd) {
return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "param %d runs past the ticket", i);
}
WriteTicketParamValue(bufferPtr, type, valuePtr, length);
if (type != paramTypes[i]) {
return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "param %d has type %04x", i, type);
}
inbuf = valuePtr + length;
}
return hleLogDebug(Log::sceNet, 0);
}
const HLEFunction sceNpAuth[] = {
+2
View File
@@ -370,6 +370,8 @@ tests_good = [
"net/inet/polledge",
"net/inet/reuse",
"net/inet/errnothread",
"net/http/ids",
"net/npauth/ticketparam",
"net/adhoc/abandon",
"net/adhoc/alerts",
"net/adhoc/apctlinit",