diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 5a8e12f7ee61..861d57b0f457 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -329,7 +329,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, co return 0; } -int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress) { +int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress, std::string * httpCode) { // Snarf all the data we can into RAM. A little unsafe but hey. static constexpr float CANCEL_INTERVAL = 0.25f; bool ready = false; @@ -367,6 +367,9 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector & return -1; } + if (httpCode) + *httpCode = line; + while (true) { int sz = readbuf->TakeLineCRLF(&line); if (!sz || sz < 0) diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index dd104e2fa603..fdbc64115fc8 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -72,7 +72,7 @@ class Client : public net::Connection { int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress); int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress); - int ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress); + int ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress, std::string* httpCode = nullptr); // If your response contains a response, you must read it. int ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, net::RequestProgress *progress); @@ -84,6 +84,10 @@ class Client : public net::Connection { userAgent_ = value; } + void SetHttpVersion(const char* version) { + httpVersion_ = version; + } + protected: std::string userAgent_; const char *httpVersion_; diff --git a/Core/HLE/FunctionWrappers.h b/Core/HLE/FunctionWrappers.h index d745818338d5..de9df22582c0 100644 --- a/Core/HLE/FunctionWrappers.h +++ b/Core/HLE/FunctionWrappers.h @@ -232,6 +232,11 @@ template void WrapI_UUUUU() { RETURN(retval); } +template void WrapI_UCUUI() { + int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3), PARAM(4)); + RETURN(retval); +} + template void WrapI_V() { int retval = func(); RETURN(retval); @@ -765,6 +770,11 @@ template void WrapU_UUUUUUU() { RETURN(retval); } +template void WrapI_UUUUUUU() { + int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6)); + RETURN(retval); +} + template void WrapI_IUUUUUU() { int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6)); RETURN(retval); diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index 6d13b6f1f2f9..47706b2f92da 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -15,68 +15,362 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include +#include +#include +#include +#include + +#include "Core/Core.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" - +#include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/sceHttp.h" +#include "Core/Debugger/MemBlockInfo.h" +#include "Common/StringUtils.h" +#include "Common/LogReporting.h" +#include "Common/Net/URL.h" #include "Common/Net/HTTPClient.h" -// If http isn't loaded (seems unlikely), most functions should return SCE_KERNEL_ERROR_LIBRARY_NOTFOUND - -// Could come in handy someday if we ever implement sceHttp* for real. -enum PSPHttpMethod { - PSP_HTTP_METHOD_GET, - PSP_HTTP_METHOD_POST, - PSP_HTTP_METHOD_HEAD -}; +static std::vector> httpObjects; +static std::mutex httpLock; -// Just a holder for settings like user agent string -class HTTPTemplate { - char useragent[512]; -}; +bool httpInited = false; +bool httpsInited = false; +bool httpCacheInited = false; -class HTTPConnection { -}; +HTTPTemplate::HTTPTemplate(const char* userAgent, int httpVer, int autoProxyConf) { + this->userAgent = userAgent ? userAgent : ""; + this->httpVer = (SceHttpVersion)httpVer; + this->autoProxyConf = (SceHttpProxyMode)autoProxyConf; +} -class HTTPRequest { +int HTTPTemplate::addRequestHeader(const char* name, const char* value, u32 mode) { + // Note: std::map doesn't support key duplication, will need std::multimap to support SCE_HTTP_HEADER_ADD mode + //if (mode != SCE_HTTP_HEADER_OVERWRITE) + // return SCE_HTTP_ERROR_NOT_SUPPORTED; // FIXME: PSP might not support mode other than SCE_HTTP_HEADER_OVERWRITE (0) -}; + // Handle User-Agent separately, since PSP Browser seems to add "User-Agent" header manually + if (mode == SCE_HTTP_HEADER_OVERWRITE) { + std::string s = name; + std::transform(s.begin(), s.end(), s.begin(), [](const unsigned char i) { return std::tolower(i); }); + if (s == "user-agent") + setUserAgent(value); + } + requestHeaders_[name] = value; + return 0; +} -int sceHttpSetResolveRetry(int connectionID, int retryCount) { - ERROR_LOG(SCENET, "UNIMPL sceHttpSetResolveRetry(%d, %d)", connectionID, retryCount); +int HTTPTemplate::removeRequestHeader(const char* name) { + requestHeaders_.erase(name); return 0; } -static int sceHttpInit(int unknown) { - ERROR_LOG(SCENET, "UNIMPL sceHttpInit(%i)", unknown); +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]); + + // Initialize + this->templateID = templateID; + this->hostString = hostString; + this->scheme = scheme; + this->port = port; + this->enableKeepalive = enableKeepalive; +} + +HTTPRequest::HTTPRequest(int connectionID, int method, const char* url, u64 contentLength) { + // 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(*httpObjects[connectionID - 1LL])); + + // Initialize + this->connectionID = connectionID; + this->method = method; + this->url = url ? url : ""; + this->contentLength = contentLength; + + //progress_.cancelled = &cancelled_; + responseContent_.clear(); +} + +HTTPRequest::~HTTPRequest() { + client.Disconnect(); + if (Memory::IsValidAddress(headerAddr_)) + userMemory.Free(headerAddr_); +} + +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; + for (std::string& line : responseHeaders_) { + if (startsWithNoCase(line, "Content-Length:")) { + size_t size_pos = line.find_first_of(' '); + if (size_pos != line.npos) { + size_pos = line.find_first_not_of(' ', size_pos); + } + if (size_pos != line.npos) { + entityLength_ = atoi(&line[size_pos]); + } + } + } + return entityLength_; +} + +int HTTPRequest::abortRequest() { + cancelled_ = true; + // FIXME: Will sceHttpAbortRequest returns an error if the request was not sent yet? + //if (progress_.progress == 0.0f) + // return SCE_HTTP_ERROR_BEFORE_SEND; + return 0; +} + +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(imploded, delim)); + const std::string& s = httpLine_ + delim + imploded.str(); + u32 sz = (u32)s.size(); + + u32* headerAddr = (u32*)Memory::GetPointerUnchecked(headerAddrPtr); + u32* headerSize = (u32*)Memory::GetPointerUnchecked(headerSizePtr); + // Resize internal header buffer (should probably be part of network memory pool?) + // FIXME: Do we still need to provides a valid address for the game even when header size is 0 ? + if (headerSize_ != sz && sz > 0) { + if (Memory::IsValidAddress(headerAddr_)) { + userMemory.Free(headerAddr_); + } + headerAddr_ = userMemory.Alloc(sz, false, "sceHttp response headers"); + headerSize_ = sz; + } + + u8* header = Memory::GetPointerWrite(headerAddr_); + DEBUG_LOG(SCENET, "headerAddr: %08x => %08x", *headerAddr, headerAddr_); + DEBUG_LOG(SCENET, "headerSize: %d => %d", *headerSize, sz); + if (!header && sz > 0) { + ERROR_LOG(SCENET, "Failed to allocate internal header buffer."); + //*headerSize = 0; + //*headerAddr = 0; + return SCE_HTTP_ERROR_OUT_OF_MEMORY; // SCE_HTTP_ERROR_TOO_LARGE_RESPONSE_HEADER + } + + if (sz > 0) { + memcpy(header, s.c_str(), sz); + NotifyMemInfo(MemBlockFlags::WRITE, headerAddr_, sz, "HttpGetAllHeader"); + } + *headerSize = sz; + NotifyMemInfo(MemBlockFlags::WRITE, headerSizePtr, 4, "HttpGetAllHeader"); + + *headerAddr = headerAddr_; + NotifyMemInfo(MemBlockFlags::WRITE, headerAddrPtr, 4, "HttpGetAllHeader"); + + DEBUG_LOG(SCENET, "Headers: %s", s.c_str()); + return 0; +} + +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); + NotifyMemInfo(MemBlockFlags::WRITE, destDataPtr, sz, "HttpReadData"); + responseContent_.erase(0, sz); + } + return sz; +} + +int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { + // Initialize Connection + client.SetDataTimeout(getRecvTimeout() / 1000000.0); + // Initialize Headers + if (getHttpVer() == SCE_HTTP_VERSION_1_0) + client.SetHttpVersion("1.0"); + else + client.SetHttpVersion("1.1"); + client.SetUserAgent(getUserAgent()); + if (postDataSize > 0) + requestHeaders_["Content-Length"] = std::to_string(postDataSize); + const std::string delimiter = "\r\n"; + const std::string extraHeaders = std::accumulate(requestHeaders_.begin(), requestHeaders_.end(), std::string(), + [delimiter](const std::string& s, const std::pair& p) { + return s + p.first + ": " + p.second + delimiter; + }); + + // TODO: Do this on a separate thread, since this may blocks "Emu" thread here + // Try to resolve first + Url fileUrl(url); + if (!fileUrl.Valid()) { + return SCE_HTTP_ERROR_INVALID_URL; + } + if (!client.Resolve(fileUrl.Host().c_str(), fileUrl.Port())) { + ERROR_LOG(IO, "Failed resolving %s", fileUrl.ToString().c_str()); + return -1; + } + + // Establish Connection + if (!client.Connect(getResolveRetryCount(), getConnectTimeout() / 1000000.0, &cancelled_)) { + ERROR_LOG(SCENET, "Failed connecting to server or cancelled."); + return -1; // SCE_HTTP_ERROR_ABORTED + } + if (cancelled_) { + return SCE_HTTP_ERROR_ABORTED; + } + + // Send the Request + std::string methodstr = "GET"; + switch (method) + { + case PSP_HTTP_METHOD_POST: + methodstr = "POST"; + break; + case PSP_HTTP_METHOD_HEAD: + methodstr = "HEAD"; + break; + default: + break; + } + net::Buffer buffer_; + net::RequestProgress progress_(&cancelled_); + http::RequestParams req(fileUrl.Resource(), "*/*"); + const char* postData = Memory::GetCharPointer(postDataPtr); + if (postDataSize > 0) + NotifyMemInfo(MemBlockFlags::READ, postDataPtr, postDataSize, "HttpSendRequest"); + int err = client.SendRequestWithData(methodstr.c_str(), req, std::string(postData ? postData : "", postData ? postDataSize : 0), extraHeaders.c_str(), &progress_); + if (cancelled_) { + return SCE_HTTP_ERROR_ABORTED; + } + if (err < 0) { + return err; // SCE_HTTP_ERROR_BAD_RESPONSE; + } + + // Retrieve Response's Status Code (and Headers too?) + responseCode_ = client.ReadResponseHeaders(&buffer_, responseHeaders_, &progress_, &httpLine_); + if (cancelled_) { + return SCE_HTTP_ERROR_ABORTED; + } + + // TODO: Read response entity within readData() in smaller chunk(based on size arg of sceHttpReadData) instead of the whole content at once here + net::Buffer entity_; + int res = client.ReadResponseEntity(&buffer_, responseHeaders_, &entity_, &progress_); + if (res != 0) { + ERROR_LOG(SCENET, "Unable to read HTTP response entity: %d", res); + } + entity_.TakeAll(&responseContent_); + if (cancelled_) { + return SCE_HTTP_ERROR_ABORTED; + } + + return 0; +} + +static void __HttpNotifyLifecycle(CoreLifecycle stage) { + if (stage == CoreLifecycle::STOPPING) { + for (const auto& it : httpObjects) { + if (it->className() == name_HTTPRequest) + (static_cast(it.get()))->abortRequest(); + } + } +} + +static void __HttpRequestStop() { + // This can happen from a separate thread. + std::lock_guard guard(httpLock); + for (const auto& it : httpObjects) { + if (it->className() == name_HTTPRequest) + (static_cast(it.get()))->abortRequest(); + } +} + +void __HttpInit() { + Core_ListenLifecycle(&__HttpNotifyLifecycle); + Core_ListenStopRequest(&__HttpRequestStop); + + // Finding memory leaks by object numbers + //_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + //_CrtSetBreakAlloc(2407638); + //_CrtSetBreakAlloc(1691236); + //_CrtSetBreakAlloc(1691235); + //_CrtSetBreakAlloc(1691231); +} + +void __HttpShutdown() { + std::lock_guard guard(httpLock); + httpObjects.clear(); +} + +// id: ID of the template or connection +int sceHttpSetResolveRetry(int id, int retryCount) { + WARN_LOG(SCENET, "UNTESTED sceHttpSetResolveRetry(%d, %d)", id, retryCount); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(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(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); + + conn->setResolveRetry(retryCount); + return 0; +} + +static int sceHttpInit(int poolSize) { + WARN_LOG(SCENET, "UNTESTED sceHttpInit(%i)", poolSize); + if (httpInited) + return hleLogError(SCENET, SCE_HTTP_ERROR_ALREADY_INITED, "http already inited"); + + std::lock_guard 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); + httpInited = true; return 0; } static int sceHttpEnd() { - ERROR_LOG(SCENET, "UNIMPL sceHttpEnd()"); + WARN_LOG(SCENET, "UNTESTED sceHttpEnd()"); + std::lock_guard guard(httpLock); + httpObjects.clear(); + httpInited = false; return 0; } static int sceHttpInitCache(int size) { ERROR_LOG(SCENET, "UNIMPL sceHttpInitCache(%d)", size); + httpCacheInited = true; return 0; } static int sceHttpEndCache() { ERROR_LOG(SCENET, "UNIMPL sceHttpEndCache()"); + httpCacheInited = false; return 0; } -static int sceHttpEnableCache(int id) { - ERROR_LOG(SCENET, "UNIMPL sceHttpEnableCache(%d)", id); +static int sceHttpEnableCache(int templateID) { + ERROR_LOG(SCENET, "UNIMPL sceHttpEnableCache(%d)", templateID); return 0; } -static int sceHttpDisableCache(int id) { - ERROR_LOG(SCENET, "UNIMPL sceHttpDisableCache(%d)", id); +// FIXME: Can be TemplateID or ConnectionID ? Megaman PoweredUp seems to use both id on sceHttpDisableCache +static int sceHttpDisableCache(int templateID) { + ERROR_LOG(SCENET, "UNIMPL sceHttpDisableCache(%d)", templateID); return 0; } @@ -86,42 +380,127 @@ static u32 sceHttpGetProxy(u32 id, u32 activateFlagPtr, u32 modePtr, u32 proxyHo } static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { - ERROR_LOG(SCENET, "UNIMPL sceHttpGetStatusCode(%d, %x)", requestID, statusCodePtr); + WARN_LOG(SCENET, "UNTESTED sceHttpGetStatusCode(%d, %x)", requestID, statusCodePtr); + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (!Memory::IsValidRange(statusCodePtr, 4)) + return hleLogError(SCENET, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; + + const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + // FIXME: According to JPCSP, try to connect the request first + //req->connect(); + int status = req->getStatusCode(); + + DEBUG_LOG(SCENET, "StatusCode = %d (in) => %d (out)", Memory::ReadUnchecked_U32(statusCodePtr), status); + Memory::WriteUnchecked_U32(status, statusCodePtr); + NotifyMemInfo(MemBlockFlags::WRITE, statusCodePtr, 4, "HttpGetStatusCode"); return 0; } +// 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) { - ERROR_LOG(SCENET, "UNIMPL sceHttpReadData(%d, %x, %x)", requestID, dataPtr, dataSize); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpReadData(%d, %x, %d)", requestID, dataPtr, dataSize); + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (!Memory::IsValidRange(dataPtr, dataSize)) + return hleLogError(SCENET, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE + + const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + // FIXME: According to JPCSP, try to connect the request first + //req->connect(); + + DEBUG_LOG(SCENET, "Entity remaining / size = %d / %d", req->getResponseRemainingContentLength(), req->getResponseContentLength()); + //if (req->getResponseContentLength()) == 0) + // return hleLogError(SCENET, SCE_HTTP_ERROR_NO_CONTENT_LENGTH, "no content length"); + int retval = req->readData(dataPtr, dataSize); + + if (retval > 0) { + u8* data = (u8*)Memory::GetPointerUnchecked(dataPtr); + std::string datahex; + DataToHexString(10, 0, data, retval, &datahex); + DEBUG_LOG(SCENET, "Data Dump (%d bytes):\n%s", retval, datahex.c_str()); + } + + // Faking latency to slow down download progressbar, since we currently downloading the full content at once instead of in chunk per sceHttpReadData's dataSize + return hleDelayResult(hleLogDebug(SCENET, retval), "fake read data latency", 5000); } +// FIXME: JPCSP didn't do anything other than appending the data into internal buffer, does sceHttpSendRequest can be called multiple times before using sceHttpGetStatusCode or sceHttpReadData? any game do this? static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { - ERROR_LOG(SCENET, "UNIMPL sceHttpSendRequest(%d, %x, %x)", requestID, dataPtr, dataSize); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpSendRequest(%d, %x, %x)", requestID, dataPtr, dataSize); + if (!httpInited) + return hleLogError(SCENET, SCE_HTTP_ERROR_BEFORE_INIT, "http not initialized yet"); + + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (dataSize > 0 && !Memory::IsValidRange(dataPtr, dataSize)) + return hleLogError(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(SCENET, retval); } static int sceHttpDeleteRequest(int requestID) { - ERROR_LOG(SCENET, "UNIMPL sceHttpDeleteRequest(%d)", requestID); + WARN_LOG(SCENET, "UNTESTED sceHttpDeleteRequest(%d)", requestID); + std::lock_guard guard(httpLock); + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[requestID - 1LL]->className() != name_HTTPRequest) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + httpObjects.erase(httpObjects.begin() + requestID - 1); return 0; } +// id: ID of the template, connection or request static int sceHttpDeleteHeader(int id, const char *name) { - ERROR_LOG(SCENET, "UNIMPL sceHttpDeleteHeader(%d, %s)", id, name); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpDeleteHeader(%d, %s)", id, safe_string(name)); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get(); + return req->removeRequestHeader(name); } static int sceHttpDeleteConnection(int connectionID) { - ERROR_LOG(SCENET, "UNIMPL sceHttpDisableCache(%d)", connectionID); + WARN_LOG(SCENET, "UNTESTED sceHttpDisableCache(%d)", connectionID); + std::lock_guard guard(httpLock); + if (connectionID <= 0 || connectionID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + httpObjects.erase(httpObjects.begin() + connectionID - 1); return 0; } +// id: ID of the template, connection or request static int sceHttpSetConnectTimeOut(int id, u32 timeout) { - ERROR_LOG(SCENET, "UNIMPL sceHttpSetConnectTimeout(%d, %d)", id, timeout); + WARN_LOG(SCENET, "UNTESTED sceHttpSetConnectTimeout(%d, %d)", id, timeout); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + auto& conn = httpObjects[id - 1LL]; + conn->setConnectTimeout(timeout); return 0; } +// id: ID of the template, connection or request static int sceHttpSetSendTimeOut(int id, u32 timeout) { ERROR_LOG(SCENET, "UNIMPL sceHttpSetSendTimeout(%d, %d)", id, timeout); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + auto& conn = httpObjects[id - 1LL]; + conn->setSendTimeout(timeout); return 0; } @@ -130,64 +509,125 @@ static u32 sceHttpSetProxy(u32 id, u32 activateFlagPtr, u32 mode, u32 newProxyHo return 0; } +// id: ID of the template or connection static int sceHttpEnableCookie(int id) { ERROR_LOG(SCENET, "UNIMPL sceHttpEnableCookie(%d)", id); return 0; } +// id: ID of the template or connection static int sceHttpEnableKeepAlive(int id) { ERROR_LOG(SCENET, "UNIMPL sceHttpEnableKeepAlive(%d)", id); return 0; } +// id: ID of the template or connection static int sceHttpDisableCookie(int id) { ERROR_LOG(SCENET, "UNIMPL sceHttpDisableCookie(%d)", id); return 0; } +// id: ID of the template or connection static int sceHttpDisableKeepAlive(int id) { ERROR_LOG(SCENET, "UNIMPL sceHttpDisableKeepAlive(%d)", id); return 0; } static int sceHttpsInit(int unknown1, int unknown2, int unknown3, int unknown4) { - ERROR_LOG(SCENET, "UNIMPL sceHttpsInit(%d, %d, %d, %d)", unknown1, unknown2, unknown3, unknown4); + ERROR_LOG(SCENET, "UNIMPL sceHttpsInit(%d, %d, %d, %x)", unknown1, unknown2, unknown3, unknown4); + httpsInited = true; + return 0; +} + +static int sceHttpsInitWithPath(int unknown1, int unknown2, int unknown3) { + ERROR_LOG(SCENET, "UNIMPL sceHttpsInitWithPath(%d, %d, %d)", unknown1, unknown2, unknown3); + httpsInited = true; return 0; } static int sceHttpsEnd() { ERROR_LOG(SCENET, "UNIMPL sceHttpsEnd()"); + httpsInited = false; + return 0; +} + +static int sceHttpsDisableOption(int id) { + ERROR_LOG(SCENET, "UNIMPL sceHttpsDisableOption(%d)", id); return 0; } // Parameter "method" should be one of PSPHttpMethod's listed entries static int sceHttpCreateRequest(int connectionID, int method, const char *path, u64 contentLength) { - ERROR_LOG(SCENET, "UNIMPL sceHttpCreateRequest(%d, %d, %s, %llx)", connectionID, method, path, contentLength); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpCreateRequest(%d, %d, %s, %llx)", connectionID, method, safe_string(path), contentLength); + std::lock_guard guard(httpLock); + if (connectionID <= 0 || connectionID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) + return hleLogError(SCENET, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method"); + + httpObjects.emplace_back(std::make_shared(connectionID, method, path? path:"", contentLength)); + int retid = (int)httpObjects.size(); + return hleLogSuccessI(SCENET, retid); } -static int sceHttpCreateConnection(int templateID, const char *hostString, const char *unknown1, u32 port, int unknown2) { - ERROR_LOG(SCENET, "UNIMPL sceHttpCreateConnection(%d, %s, %s, %d, %d)", templateID, hostString, unknown1, port, unknown2); - return 0; +// FIXME: port type is probably u16 +static int sceHttpCreateConnection(int templateID, const char *hostString, const char *scheme, u32 port, int enableKeepalive) { + WARN_LOG(SCENET, "UNTESTED sceHttpCreateConnection(%d, %s, %s, %d, %d)", templateID, safe_string(hostString), safe_string(scheme), port, enableKeepalive); + std::lock_guard guard(httpLock); + if (templateID <= 0 || templateID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + httpObjects.emplace_back(std::make_shared(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive)); + int retid = (int)httpObjects.size(); + return hleLogSuccessI(SCENET, retid); } static int sceHttpGetNetworkErrno(int request, u32 errNumPtr) { ERROR_LOG(SCENET, "UNIMPL sceHttpGetNetworkErrno(%d, %x)", request, errNumPtr); + if (Memory::IsValidRange(errNumPtr, 4)) { + INFO_LOG(SCENET, "Input errNum = %d", Memory::ReadUnchecked_U32(errNumPtr)); + Memory::WriteUnchecked_U32(0, errNumPtr); // dummy error code 0 (no error?) + NotifyMemInfo(MemBlockFlags::WRITE, errNumPtr, 4, "HttpGetNetworkErrno"); + } return 0; } +// id: ID of the template, connection or request static int sceHttpAddExtraHeader(int id, const char *name, const char *value, int unknown) { - ERROR_LOG(SCENET, "UNIMPL sceHttpAddExtraHeader(%d, %s, %s, %d)", id, name, value, unknown); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpAddExtraHeader(%d, %s, %s, %d)", id, safe_string(name), safe_string(value), unknown); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get(); + return req->addRequestHeader(name, value, unknown); } static int sceHttpAbortRequest(int requestID) { - ERROR_LOG(SCENET, "UNIMPL sceHttpAbortRequest(%d)", requestID); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpAbortRequest(%d)", requestID); + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + return req->abortRequest(); } static int sceHttpDeleteTemplate(int templateID) { - ERROR_LOG(SCENET, "UNIMPL sceHttpDeleteTemplate(%d)", templateID); + WARN_LOG(SCENET, "UNTESTED sceHttpDeleteTemplate(%d)", templateID); + std::lock_guard guard(httpLock); + if (templateID <= 0 || templateID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + httpObjects.erase(httpObjects.begin() + templateID - 1); return 0; } @@ -196,8 +636,35 @@ static int sceHttpSetMallocFunction(u32 mallocFuncPtr, u32 freeFuncPtr, u32 real return 0; } +// id: ID of the template or connection static int sceHttpSetResolveTimeOut(int id, u32 timeout) { ERROR_LOG(SCENET, "UNIMPL sceHttpSetResolveTimeOut(%d, %d)", id, timeout); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(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(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); + + conn->setResolveTimeout(timeout); + return 0; +} + +//typedef int(* SceHttpsCallback) (unsigned int verifyEsrr, void *const sslCert[], int certNum, void *userArg) +static int sceHttpsSetSslCallback(int id, u32 callbackFuncPtr, u32 userArgPtr) { + ERROR_LOG(SCENET, "UNIMPL sceHttpsSetSslCallback(%d, %x, %x)", id, callbackFuncPtr, userArgPtr); + return 0; +} + +//typedef int(*SceHttpRedirectCallback) (int request, int statusCode, int* method, const char* location, void* userArg); +static int sceHttpSetRedirectCallback(int requestID, u32 callbackFuncPtr, u32 userArgPtr) { + ERROR_LOG(SCENET, "UNIMPL sceHttpSetRedirectCallback(%d, %x, %x)", requestID, callbackFuncPtr, userArgPtr); + return 0; +} + +//typedef int(*SceHttpAuthInfoCallback) (int request, SceHttpAuthType authType, const char* realm, char* username, char* password, int needEntity, unsigned char** entityBody, unsigned int* entitySize, int* save, void* userArg); +static int sceHttpSetAuthInfoCallback(int id, u32 callbackFuncPtr, u32 userArgPtr) { + ERROR_LOG(SCENET, "UNIMPL sceHttpSetAuthInfoCallback(%d, %x, %x)", id, callbackFuncPtr, userArgPtr); return 0; } @@ -206,23 +673,25 @@ static int sceHttpSetAuthInfoCB(int id, u32 callbackFuncPtr) { return 0; } +// id: ID of the template or connection static int sceHttpEnableRedirect(int id) { ERROR_LOG(SCENET, "UNIMPL sceHttpEnableRedirect(%d)", id); return 0; } -static int sceHttpEnableAuth(int id) { - ERROR_LOG(SCENET, "UNIMPL sceHttpEnableAuth(%d)", id); +static int sceHttpEnableAuth(int templateID) { + ERROR_LOG(SCENET, "UNIMPL sceHttpEnableAuth(%d)", templateID); return 0; } +// id: ID of the template or connection static int sceHttpDisableRedirect(int id) { ERROR_LOG(SCENET, "UNIMPL sceHttpDisableRedirect(%d)", id); return 0; } -static int sceHttpDisableAuth(int id) { - ERROR_LOG(SCENET, "UNIMPL sceHttpDisableAuth(%d)", id); +static int sceHttpDisableAuth(int templateID) { + ERROR_LOG(SCENET, "UNIMPL sceHttpDisableAuth(%d)", templateID); return 0; } @@ -241,42 +710,118 @@ static int sceHttpLoadSystemCookie() { return 0; } -static int sceHttpCreateTemplate(const char *agent, int unknown1, int unknown2) { - ERROR_LOG(SCENET, "UNIMPL sceHttpCreateTemplate(%s, %d, %d)", agent, unknown1, unknown2); - return 0; +// PSP Browser seems to set userAgent to 0 and later set the User-Agent header using sceHttpAddExtraHeader +static int sceHttpCreateTemplate(const char *userAgent, int httpVer, int autoProxyConf) { + WARN_LOG(SCENET, "UNTESTED sceHttpCreateTemplate(%s, %d, %d) at %08x", safe_string(userAgent), httpVer, autoProxyConf, currentMIPS->pc); + // Reporting to find more games to be tested + DEBUG_LOG_REPORT_ONCE(sceHttpCreateTemplate, SCENET, "UNTESTED sceHttpCreateTemplate(%s, %d, %d)", safe_string(userAgent), httpVer, autoProxyConf); + std::lock_guard guard(httpLock); + httpObjects.push_back(std::make_shared(userAgent? userAgent:"", httpVer, autoProxyConf)); + int retid = (int)httpObjects.size(); + return hleLogSuccessI(SCENET, retid); } // Parameter "method" should be one of PSPHttpMethod's listed entries static int sceHttpCreateRequestWithURL(int connectionID, int method, const char *url, u64 contentLength) { - ERROR_LOG(SCENET, "UNIMPL sceHttpCreateRequestWithURL(%d, %d, %s, %llx)", connectionID, method, url, contentLength); - return 0; + WARN_LOG(SCENET, "UNTESTED sceHttpCreateRequestWithURL(%d, %d, %s, %llx)", connectionID, method, safe_string(url), contentLength); + std::lock_guard guard(httpLock); + if (connectionID <= 0 || connectionID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) + return hleLogError(SCENET, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method"); + + Url baseURL(url ? url : ""); + if (!baseURL.Valid()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_URL, "invalid url"); + + httpObjects.emplace_back(std::make_shared(connectionID, method, url? url:"", contentLength)); + int retid = (int)httpObjects.size(); + return hleLogSuccessI(SCENET, retid); } -static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int unknown1) { - ERROR_LOG(SCENET, "UNIMPL sceHttpCreateConnectionWithURL(%d, %s, %d)", templateID, url, unknown1); - return 0; +static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int enableKeepalive) { + WARN_LOG(SCENET, "UNTESTED sceHttpCreateConnectionWithURL(%d, %s, %d)", templateID, safe_string(url), enableKeepalive); + std::lock_guard guard(httpLock); + if (templateID <= 0 || templateID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + Url baseURL(url? url: ""); + if (!baseURL.Valid()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_URL, "invalid url"); + + httpObjects.emplace_back(std::make_shared(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive)); + int retid = (int)httpObjects.size(); + return hleLogSuccessI(SCENET, retid); } +// id: ID of the template or connection static int sceHttpSetRecvTimeOut(int id, u32 timeout) { - ERROR_LOG(SCENET, "UNIMPL sceHttpSetRecvTimeOut(%d, %x)", id, timeout); + WARN_LOG(SCENET, "UNTESTED sceHttpSetRecvTimeOut(%d, %d)", id, timeout); + if (id <= 0 || id > httpObjects.size()) + return hleLogError(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(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); + + conn->setRecvTimeout(timeout); return 0; } -static int sceHttpGetAllHeader(int request, u32 headerPtrToPtr, u32 headerSize) { - ERROR_LOG(SCENET, "UNIMPL sceHttpGetAllHeader(%d, %x, %x)", request, headerPtrToPtr, headerSize); - return 0; +// FIXME: Headers should includes the "HTTP/MajorVer.MinorVer StatusCode Comment" line? so PSP Browser can parse it using sceParseHttpStatusLine +// 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(SCENET, "UNTESTED sceHttpGetAllHeader(%d, %x, %x)", requestID, headerAddrPtr, headerSizePtr); + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (!Memory::IsValidRange(headerAddrPtr, 4)) + return hleLogError(SCENET, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; + + if (!Memory::IsValidRange(headerSizePtr, 4)) + return hleLogError(SCENET, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; + + const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + // FIXME: According to JPCSP, try to connect the request first + //req->connect(); + int retval = req->getAllResponseHeaders(headerAddrPtr, headerSizePtr); + return hleLogDebug(SCENET, retval); } -static int sceHttpGetContentLength(int requestID, u64 contentLengthPtr) { - ERROR_LOG(SCENET, "UNIMPL sceHttpGetContentLength(%d, %llx)", requestID, contentLengthPtr); +// 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(SCENET, "UNTESTED sceHttpGetContentLength(%d, %x)", requestID, contentLengthPtr); + if (requestID <= 0 || requestID > httpObjects.size()) + return hleLogError(SCENET, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + + if (!Memory::IsValidRange(contentLengthPtr, 8)) + return hleLogError(SCENET, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; + + const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + // FIXME: According to JPCSP, try to connect the request first + //req->connect(); + int len = req->getResponseContentLength(); + if (len < 0) + return hleLogError(SCENET, SCE_HTTP_ERROR_NO_CONTENT_LENGTH, "no content length"); + + DEBUG_LOG(SCENET, "ContentLength = %lld (in) => %lld (out)", Memory::Read_U64(contentLengthPtr), (u64)len); + Memory::Write_U64((u64)len, contentLengthPtr); + NotifyMemInfo(MemBlockFlags::WRITE, contentLengthPtr, 8, "HttpGetContentLength"); return 0; } /* -* 0x62411801 sceSircsInit +0x62411801 sceSircsInit 0x19155a2f sceSircsEnd 0x71eef62d sceSircsSend - */ +*/ const HLEFunction sceHttp[] = { {0XAB1ABE07, &WrapI_I, "sceHttpInit", 'i', "i" }, {0XD1C8945E, &WrapI_V, "sceHttpEnd", 'i', "" }, @@ -322,15 +867,15 @@ const HLEFunction sceHttp[] = { {0XCDF8ECB9, &WrapI_ICI, "sceHttpCreateConnectionWithURL", 'i', "isi" }, {0X1F0FC3E3, &WrapI_IU, "sceHttpSetRecvTimeOut", 'i', "ix" }, {0XDB266CCF, &WrapI_IUU, "sceHttpGetAllHeader", 'i', "ixx" }, - {0X0282A3BD, &WrapI_IU64, "sceHttpGetContentLength", 'i', "iX" }, + {0X0282A3BD, &WrapI_IU, "sceHttpGetContentLength", 'i', "ix" }, {0X7774BF4C, nullptr, "sceHttpAddCookie", '?', "" }, - {0X68AB0F86, nullptr, "sceHttpsInitWithPath", '?', "" }, - {0XB3FAF831, nullptr, "sceHttpsDisableOption", '?', "" }, + {0X68AB0F86, &WrapI_III, "sceHttpsInitWithPath", 'i', "iii" }, + {0XB3FAF831, &WrapI_I, "sceHttpsDisableOption", 'i', "i" }, {0X2255551E, nullptr, "sceHttpGetNetworkPspError", '?', "" }, {0XAB1540D5, nullptr, "sceHttpsGetSslError", '?', "" }, - {0XA4496DE5, nullptr, "sceHttpSetRedirectCallback", '?', "" }, - {0X267618F4, nullptr, "sceHttpSetAuthInfoCallback", '?', "" }, - {0X569A1481, nullptr, "sceHttpsSetSslCallback", '?', "" }, + {0XA4496DE5, &WrapI_IUU, "sceHttpSetRedirectCallback", 'i', "ixx" }, + {0X267618F4, &WrapI_IUU, "sceHttpSetAuthInfoCallback", 'i', "ixx" }, + {0X569A1481, &WrapI_IUU, "sceHttpsSetSslCallback", 'i', "ixx" }, {0XBAC31BF1, nullptr, "sceHttpsEnableOption", '?', "" }, }; diff --git a/Core/HLE/sceHttp.h b/Core/HLE/sceHttp.h index e18f02e36ca9..b8d3b142873e 100644 --- a/Core/HLE/sceHttp.h +++ b/Core/HLE/sceHttp.h @@ -17,6 +17,275 @@ #pragma once -int sceHttpSetResolveRetry(int connectionID, int retryCount); +#include "Common/Net/HTTPClient.h" -void Register_sceHttp(); \ No newline at end of file +// Based on https://docs.vitasdk.org/group__SceHttpUser.html +#define SCE_HTTP_DEFAULT_RESOLVER_TIMEOUT (1 * 1000 * 1000U) +#define SCE_HTTP_DEFAULT_RESOLVER_RETRY (5U) +#define SCE_HTTP_DEFAULT_CONNECT_TIMEOUT (30* 1000 * 1000U) +#define SCE_HTTP_DEFAULT_SEND_TIMEOUT (120* 1000 * 1000U) +#define SCE_HTTP_DEFAULT_RECV_TIMEOUT (120* 1000 * 1000U) +#define SCE_HTTP_DEFAULT_RECV_BLOCK_SIZE (1500U) +#define SCE_HTTP_DEFAULT_RESPONSE_HEADER_MAX (5000U) +#define SCE_HTTP_DEFAULT_REDIRECT_MAX (6U) +#define SCE_HTTP_DEFAULT_TRY_AUTH_MAX (6U) +#define SCE_HTTP_INVALID_ID 0 +#define SCE_HTTP_ENABLE (1) +#define SCE_HTTP_DISABLE (0) +#define SCE_HTTP_USERNAME_MAX_SIZE 256 +#define SCE_HTTP_PASSWORD_MAX_SIZE 256 + +// If http isn't loaded (seems unlikely), most functions should return SCE_KERNEL_ERROR_LIBRARY_NOTFOUND + +// lib_http specific error codes, based on https://uofw.github.io/uofw/lib__http_8h_source.html, combined with https://github.com/vitasdk/vita-headers/blob/master/include/psp2/net/http.h +enum SceHttpErrorCode { + SCE_HTTP_ERROR_BEFORE_INIT = 0x80431001, + SCE_HTTP_ERROR_NOT_SUPPORTED = 0x80431004, + SCE_HTTP_ERROR_ALREADY_INITED = 0x80431020, + SCE_HTTP_ERROR_BUSY = 0x80431021, + SCE_HTTP_ERROR_OUT_OF_MEMORY = 0x80431022, + SCE_HTTP_ERROR_NOT_FOUND = 0x80431025, + + SCE_HTTP_ERROR_UNKNOWN_SCHEME = 0x80431061, + SCE_HTTP_ERROR_NETWORK = 0x80431063, + SCE_HTTP_ERROR_BAD_RESPONSE = 0x80431064, + SCE_HTTP_ERROR_BEFORE_SEND = 0x80431065, + SCE_HTTP_ERROR_AFTER_SEND = 0x80431066, + SCE_HTTP_ERROR_TIMEOUT = 0x80431068, + SCE_HTTP_ERROR_UNKOWN_AUTH_TYPE = 0x80431069, + SCE_HTTP_ERROR_INVALID_VERSION = 0x8043106A, + SCE_HTTP_ERROR_UNKNOWN_METHOD = 0x8043106B, + SCE_HTTP_ERROR_READ_BY_HEAD_METHOD = 0x8043106F, + SCE_HTTP_ERROR_NOT_IN_COM = 0x80431070, + SCE_HTTP_ERROR_NO_CONTENT_LENGTH = 0x80431071, + SCE_HTTP_ERROR_CHUNK_ENC = 0x80431072, + SCE_HTTP_ERROR_TOO_LARGE_RESPONSE_HEADER = 0x80431073, + SCE_HTTP_ERROR_SSL = 0x80431075, + SCE_HTTP_ERROR_INSUFFICIENT_HEAPSIZE = 0x80431077, + SCE_HTTP_ERROR_BEFORE_COOKIE_LOAD = 0x80431078, + SCE_HTTP_ERROR_ABORTED = 0x80431080, + SCE_HTTP_ERROR_UNKNOWN = 0x80431081, + + SCE_HTTP_ERROR_INVALID_ID = 0x80431100, + SCE_HTTP_ERROR_OUT_OF_SIZE = 0x80431104, + SCE_HTTP_ERROR_INVALID_VALUE = 0x804311FE, + + SCE_HTTP_ERROR_PARSE_HTTP_NOT_FOUND = 0x80432025, + SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE = 0x80432060, + SCE_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE = 0x804321FE, + + SCE_HTTP_ERROR_INVALID_URL = 0x80433060, + + SCE_HTTP_ERROR_RESOLVER_EPACKET = 0x80436001, + SCE_HTTP_ERROR_RESOLVER_ENODNS = 0x80436002, + SCE_HTTP_ERROR_RESOLVER_ETIMEDOUT = 0x80436003, + SCE_HTTP_ERROR_RESOLVER_ENOSUPPORT = 0x80436004, + SCE_HTTP_ERROR_RESOLVER_EFORMAT = 0x80436005, + SCE_HTTP_ERROR_RESOLVER_ESERVERFAILURE = 0x80436006, + SCE_HTTP_ERROR_RESOLVER_ENOHOST = 0x80436007, + SCE_HTTP_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x80436008, + SCE_HTTP_ERROR_RESOLVER_ESERVERREFUSED = 0x80436009, + SCE_HTTP_ERROR_RESOLVER_ENORECORD = 0x8043600A +}; + +// lib_https specific error codes, based on https://uofw.github.io/uofw/lib__https_8h_source.html, combined with https://github.com/vitasdk/vita-headers/blob/master/include/psp2/net/http.h +enum SceHttpsErrorCode { + SCE_HTTPS_ERROR_OUT_OF_MEMORY = 0x80435022, + SCE_HTTPS_ERROR_CERT = 0x80435060, + SCE_HTTPS_ERROR_HANDSHAKE = 0x80435061, + SCE_HTTPS_ERROR_IO = 0x80435062, + SCE_HTTPS_ERROR_INTERNAL = 0x80435063, + SCE_HTTPS_ERROR_PROXY = 0x80435064 +}; + +// Could come in handy someday if we ever implement sceHttp* for real. +enum PSPHttpMethod { + PSP_HTTP_METHOD_GET, + PSP_HTTP_METHOD_POST, + PSP_HTTP_METHOD_HEAD +}; + +// Based on https://github.com/vitasdk/vita-headers/blob/master/include/psp2/net/http.h +enum SceHttpStatusCode { + SCE_HTTP_STATUS_CODE_CONTINUE = 100, + SCE_HTTP_STATUS_CODE_SWITCHING_PROTOCOLS = 101, + SCE_HTTP_STATUS_CODE_PROCESSING = 102, + SCE_HTTP_STATUS_CODE_OK = 200, + SCE_HTTP_STATUS_CODE_CREATED = 201, + SCE_HTTP_STATUS_CODE_ACCEPTED = 202, + SCE_HTTP_STATUS_CODE_NON_AUTHORITATIVE_INFORMATION = 203, + SCE_HTTP_STATUS_CODE_NO_CONTENT = 204, + SCE_HTTP_STATUS_CODE_RESET_CONTENT = 205, + SCE_HTTP_STATUS_CODE_PARTIAL_CONTENT = 206, + SCE_HTTP_STATUS_CODE_MULTI_STATUS = 207, + SCE_HTTP_STATUS_CODE_MULTIPLE_CHOICES = 300, + SCE_HTTP_STATUS_CODE_MOVED_PERMANENTLY = 301, + SCE_HTTP_STATUS_CODE_FOUND = 302, + SCE_HTTP_STATUS_CODE_SEE_OTHER = 303, + SCE_HTTP_STATUS_CODE_NOT_MODIFIED = 304, + SCE_HTTP_STATUS_CODE_USE_PROXY = 305, + SCE_HTTP_STATUS_CODE_TEMPORARY_REDIRECT = 307, + SCE_HTTP_STATUS_CODE_BAD_REQUEST = 400, + SCE_HTTP_STATUS_CODE_UNAUTHORIZED = 401, + SCE_HTTP_STATUS_CODE_PAYMENT_REQUIRED = 402, + SCE_HTTP_STATUS_CODE_FORBIDDDEN = 403, + SCE_HTTP_STATUS_CODE_NOT_FOUND = 404, + SCE_HTTP_STATUS_CODE_METHOD_NOT_ALLOWED = 405, + SCE_HTTP_STATUS_CODE_NOT_ACCEPTABLE = 406, + SCE_HTTP_STATUS_CODE_PROXY_AUTHENTICATION_REQUIRED = 407, + SCE_HTTP_STATUS_CODE_REQUEST_TIME_OUT = 408, + SCE_HTTP_STATUS_CODE_CONFLICT = 409, + SCE_HTTP_STATUS_CODE_GONE = 410, + SCE_HTTP_STATUS_CODE_LENGTH_REQUIRED = 411, + SCE_HTTP_STATUS_CODE_PRECONDITION_FAILED = 412, + SCE_HTTP_STATUS_CODE_REQUEST_ENTITY_TOO_LARGE = 413, + SCE_HTTP_STATUS_CODE_REQUEST_URI_TOO_LARGE = 414, + SCE_HTTP_STATUS_CODE_UNSUPPORTED_MEDIA_TYPE = 415, + SCE_HTTP_STATUS_CODE_REQUEST_RANGE_NOT_SATISFIBLE = 416, + SCE_HTTP_STATUS_CODE_EXPECTATION_FAILED = 417, + SCE_HTTP_STATUS_CODE_UNPROCESSABLE_ENTITY = 422, + SCE_HTTP_STATUS_CODE_LOCKED = 423, + SCE_HTTP_STATUS_CODE_FAILED_DEPENDENCY = 424, + SCE_HTTP_STATUS_CODE_UPGRADE_REQUIRED = 426, + SCE_HTTP_STATUS_CODE_INTERNAL_SERVER_ERROR = 500, + SCE_HTTP_STATUS_CODE_NOT_IMPLEMENTED = 501, + SCE_HTTP_STATUS_CODE_BAD_GATEWAY = 502, + SCE_HTTP_STATUS_CODE_SERVICE_UNAVAILABLE = 503, + SCE_HTTP_STATUS_CODE_GATEWAY_TIME_OUT = 504, + SCE_HTTP_STATUS_CODE_HTTP_VERSION_NOT_SUPPORTED = 505, + SCE_HTTP_STATUS_CODE_INSUFFICIENT_STORAGE = 507 +}; + +enum SceHttpVersion { + SCE_HTTP_VERSION_1_0 = 1, + SCE_HTTP_VERSION_1_1 +}; + +enum SceHttpProxyMode { + SCE_HTTP_PROXY_AUTO, + SCE_HTTP_PROXY_MANUAL +}; + +enum SceHttpAddHeaderMode { + SCE_HTTP_HEADER_OVERWRITE, + SCE_HTTP_HEADER_ADD +}; + + +// Just a holder for class names +static const char* name_HTTPTemplate = "HTTPTemplate"; +static const char* name_HTTPConnection = "HTTPConnection"; +static const char* name_HTTPRequest = "HTTPRequest"; + +class HTTPTemplate { +protected: + std::string userAgent; // char userAgent[512]; + SceHttpVersion httpVer = SCE_HTTP_VERSION_1_0; + SceHttpProxyMode autoProxyConf = SCE_HTTP_PROXY_AUTO; + + int useCookie = 0; + int useKeepAlive = 0; + int useCache = 0; + int useAuth = 0; + int useRedirect = 0; + + u32 connectTimeout = SCE_HTTP_DEFAULT_CONNECT_TIMEOUT; + u32 sendTimeout = SCE_HTTP_DEFAULT_SEND_TIMEOUT; + u32 recvTimeout = SCE_HTTP_DEFAULT_RECV_TIMEOUT; + u32 resolveTimeout = SCE_HTTP_DEFAULT_RESOLVER_TIMEOUT; + int resolveRetryCount = SCE_HTTP_DEFAULT_RESOLVER_RETRY; + + std::map requestHeaders_; + +public: + HTTPTemplate() {} + HTTPTemplate(const char* userAgent, int httpVer, int autoProxyConf); + virtual ~HTTPTemplate() = default; + + virtual const char* className() { return name_HTTPTemplate; } // to be more consistent, unlike typeid(v).name() which may varies among different compilers and requires RTTI + + const std::string getUserAgent() { return userAgent; } + int getHttpVer() { return httpVer; } + int getAutoProxyConf() { return autoProxyConf; } + + u32 getConnectTimeout() { return connectTimeout; } + u32 getSendTimeout() { return sendTimeout; } + u32 getRecvTimeout() { return recvTimeout; } + u32 getResolveTimeout() { return resolveTimeout; } + int getResolveRetryCount() { return resolveRetryCount; } + + void setUserAgent(const char* userAgent) { this->userAgent = userAgent ? userAgent : ""; } + void setConnectTimeout(u32 timeout) { this->connectTimeout = timeout; } + void setSendTimeout(u32 timeout) { this->sendTimeout = timeout; } + void setRecvTimeout(u32 timeout) { this->recvTimeout = timeout; } + void setResolveTimeout(u32 timeout) { this->resolveTimeout = timeout; } + void setResolveRetry(u32 retryCount) { this->resolveRetryCount = retryCount; } + + int addRequestHeader(const char* name, const char* value, u32 mode); + int removeRequestHeader(const char* name); +}; + +class HTTPConnection : public HTTPTemplate { +protected: + int templateID = 0; + std::string hostString; + std::string scheme; + u16 port = 80; + int enableKeepalive = 0; + +public: + HTTPConnection() {} + HTTPConnection(int templateID, const char* hostString, const char* scheme, u32 port, int enableKeepalive); + virtual ~HTTPConnection() = default; + + virtual const char* className() override { return name_HTTPConnection; } + + int getTemplateID() { return templateID; } + const std::string getHost() { return hostString; } + const std::string getScheme() { return scheme; } + u16 getPort() { return port; } + int getKeepAlive() { return enableKeepalive; } +}; + +class HTTPRequest : public HTTPConnection { +private: + int connectionID; + int method; + u64 contentLength; + std::string url; + + u32 headerAddr_ = 0; + u32 headerSize_ = 0; + bool cancelled_ = false; + int responseCode_ = -1; + int entityLength_ = -1; + + http::Client client; + //net::RequestProgress progress_(&cancelled_); + std::vector responseHeaders_; + std::string httpLine_; + std::string responseContent_; + +public: + HTTPRequest(int connectionID, int method, const char* url, u64 contentLength); + ~HTTPRequest(); + + virtual const char* className() override { return name_HTTPRequest; } + + void setInternalHeaderAddr(u32 addr) { headerAddr_ = addr; } + int getConnectionID() { return connectionID; } + int getResponseRemainingContentLength() { return (int)responseContent_.size(); } + + int getResponseContentLength(); + int abortRequest(); + int getStatusCode(); + int getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr); + int readData(u32 destDataPtr, u32 size); + int sendRequest(u32 postDataPtr, u32 postDataSize); +}; + + +void __HttpInit(); +void __HttpShutdown(); + +void Register_sceHttp(); diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 40fc161a01b4..91afaa1a901a 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -85,6 +85,7 @@ #include "sceDmac.h" #include "sceMp4.h" #include "sceOpenPSID.h" +#include "sceHttp.h" #include "Core/Util/PPGeDraw.h" /* @@ -150,6 +151,7 @@ void __KernelInit() __UsbCamInit(); __UsbMicInit(); __OpenPSIDInit(); + __HttpInit(); SaveState::Init(); // Must be after IO, as it may create a directory Reporting::Init(); @@ -173,6 +175,7 @@ void __KernelShutdown() hleCurrentThreadName = NULL; kernelObjects.Clear(); + __HttpShutdown(); __OpenPSIDShutdown(); __UsbCamShutdown(); __UsbMicShutdown(); diff --git a/Core/HLE/sceParseHttp.cpp b/Core/HLE/sceParseHttp.cpp index 7da109f4afdf..40b5cfd03419 100644 --- a/Core/HLE/sceParseHttp.cpp +++ b/Core/HLE/sceParseHttp.cpp @@ -15,16 +15,85 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include +#include + #include "Core/HLE/HLE.h" #include "Core/HLE/sceParseHttp.h" +#include "Core/HLE/FunctionWrappers.h" +#include "Core/Debugger/MemBlockInfo.h" + + +static int sceParseHttpStatusLine(u32 headerAddr, u32 headerLength, u32 httpVersionMajorAddr, u32 httpVersionMinorAddr, u32 httpStatusCodeAddr, u32 httpStatusCommentAddr, u32 httpStatusCommentLengthAddr) { + WARN_LOG(SCENET, "UNTESTED sceParseHttpStatusLine(%x, %d, %x, %x, %x, %x, %x) at %08x", headerAddr, headerLength, httpVersionMajorAddr, httpVersionMinorAddr, httpStatusCodeAddr, httpStatusCommentAddr, httpStatusCommentLengthAddr, currentMIPS->pc); + if (!Memory::IsValidRange(headerAddr, headerLength)) + return hleLogError(SCENET, -1, "invalid arg"); + + std::string headers(Memory::GetCharPointer(headerAddr), headerLength); + // Get first line from header, line ending can be '\n' or '\r' + std::istringstream hdr(headers); + std::string line; + std::getline(hdr, line, '\n'); + hdr.str(line); + std::getline(hdr, line, '\r'); + NotifyMemInfo(MemBlockFlags::READ, headerAddr, (u32)line.size(), "ParseHttpStatusLine"); + DEBUG_LOG(SCENET, "Headers: %s", headers.c_str()); + + // Split the string by pattern, based on JPCSP + std::regex rgx("HTTP/(\\d)\\.(\\d)\\s+(\\d+)(.*)"); + std::smatch matches; + + if (!std::regex_search(line, matches, rgx)) + return hleLogError(SCENET, -1, "invalid arg"); // SCE_HTTP_ERROR_PARSE_HTTP_NOT_FOUND + + try { + // Convert and Store the value + if (Memory::IsValidRange(httpVersionMajorAddr, 4)) { + Memory::WriteUnchecked_U32(stoi(matches[1].str()), httpVersionMajorAddr); + NotifyMemInfo(MemBlockFlags::WRITE, httpVersionMajorAddr, 4, "ParseHttpStatusLine"); + } + + if (Memory::IsValidRange(httpVersionMinorAddr, 4)) { + Memory::WriteUnchecked_U32(stoi(matches[2].str()), httpVersionMinorAddr); + NotifyMemInfo(MemBlockFlags::WRITE, httpVersionMinorAddr, 4, "ParseHttpStatusLine"); + } + + if (Memory::IsValidRange(httpStatusCodeAddr, 4)) { + Memory::WriteUnchecked_U32(stoi(matches[3].str()), httpStatusCodeAddr); + NotifyMemInfo(MemBlockFlags::WRITE, httpStatusCodeAddr, 4, "ParseHttpStatusLine"); + } + + std::string sc = matches[4].str(); + if (Memory::IsValidRange(httpStatusCommentAddr, 4)) { + Memory::WriteUnchecked_U32(headerAddr + (int)headers.find(sc), httpStatusCommentAddr); + NotifyMemInfo(MemBlockFlags::WRITE, httpStatusCommentAddr, 4, "ParseHttpStatusLine"); + } + + if (Memory::IsValidRange(httpStatusCommentLengthAddr, 4)) { + Memory::WriteUnchecked_U32((u32)sc.size(), httpStatusCommentLengthAddr); + NotifyMemInfo(MemBlockFlags::WRITE, httpStatusCommentLengthAddr, 4, "ParseHttpStatusLine"); + } + } + catch (const std::runtime_error& ex) { + hleLogError(SCENET, -1, "Runtime error: %s", ex.what()); + } + catch (const std::exception& ex) { + hleLogError(SCENET, -1, "Error occurred: %s", ex.what()); // SCE_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE + } + catch (...) { + hleLogError(SCENET, -1, "Unknown error"); + } + + return 0; +} const HLEFunction sceParseHttp [] = { - {0X8077A433, nullptr, "sceParseHttpStatusLine", '?', ""}, - {0XAD7BFDEF, nullptr, "sceParseHttpResponseHeader", '?', ""}, + {0X8077A433, &WrapI_UUUUUUU, "sceParseHttpStatusLine", 'i', "xxxxxxx"}, + {0XAD7BFDEF, nullptr, "sceParseHttpResponseHeader", '?', "" }, }; void Register_sceParseHttp() { RegisterModule("sceParseHttp", ARRAY_SIZE(sceParseHttp), sceParseHttp); -} \ No newline at end of file +} diff --git a/Core/HLE/sceParseUri.cpp b/Core/HLE/sceParseUri.cpp index b8b5e15cd6e7..297005662695 100644 --- a/Core/HLE/sceParseUri.cpp +++ b/Core/HLE/sceParseUri.cpp @@ -17,16 +17,139 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/sceParseUri.h" +#include "Core/HLE/FunctionWrappers.h" +#include "Core/Debugger/MemBlockInfo.h" +#include "Common/Net/URL.h" +#include "Common/StringUtils.h" + + +int workAreaAddString(u32 workAreaAddr, int workAreaSize, int offset, const char* s) { + const std::string str = (s? s:""); + + int length = (int)str.length() + 1; // added space for null termination + if (offset + length > workAreaSize) { + length = workAreaSize - offset; + if (length <= 0) { + return offset; + } + } + + Memory::MemcpyUnchecked(workAreaAddr + offset, str.c_str(), length); + return offset + length; +} + +// FIXME: parsedUriArea, workArea, and workAreaSizeAddr can be 0/null? +static int sceUriParse(u32 parsedUriAreaAddr, const char* url, u32 workAreaAddr, u32 workAreaSizeAddr, int workAreaSize) { + WARN_LOG(SCENET, "UNTESTED sceUriParse(%x, %s, %x, %x, %d) at %08x", parsedUriAreaAddr, safe_string(url), workAreaAddr, workAreaSizeAddr, workAreaSize, currentMIPS->pc); + if (url == nullptr) + return hleLogError(SCENET, -1, "invalid arg"); + + if (!Memory::IsValidRange(workAreaSizeAddr, 4)) + return hleLogError(SCENET, -1, "invalid arg"); + + // Size returner + if (parsedUriAreaAddr == 0 || workAreaAddr == 0) { + // Based on JPCSP: The required workArea size if maximum the size if the URL + 7 times the null-byte for string termination. + int urllen = (int)strlen(url); + Memory::WriteUnchecked_U32(urllen + 7, workAreaSizeAddr); + NotifyMemInfo(MemBlockFlags::WRITE, workAreaSizeAddr, 4, "UriParse"); + return hleLogSuccessI(SCENET, 0, "workAreaSize: %d, %d", urllen + 7, workAreaSize); + } + + auto parsedUri = PSPPointer::Create(parsedUriAreaAddr); + if (!parsedUri.IsValid()) + return hleLogError(SCENET, -1, "invalid arg"); + + // Parse the URL into URI components + // Full URI = scheme ":" ["//" [username [":" password] "@"] host [":" port]] path ["?" query] ["#" fragment] + Url uri(url); + // FIXME: URI without "://" should be valid too, due to PSPParsedUri.noSlash property + if (!uri.Valid()) { + return hleLogError(SCENET, -1, "invalid arg"); + } + + // Parse Host() into userInfo (in the format ":") and host + std::string host = uri.Host(); + std::string userInfoUserName = ""; + std::string userInfoPassword = ""; + // Extract Host + size_t pos = host.find('@'); + if (pos <= host.size()) { + userInfoUserName = host.substr(0, pos); + host.erase(0, pos + 1LL); // removing the "@" + } + // Extract UserName and Password + pos = userInfoUserName.find(':'); + if (pos <= userInfoUserName.size()) { + userInfoPassword = userInfoUserName.substr(pos + 1LL); // removing the ":" + userInfoUserName.erase(pos); + } + + // Parse Resource() into path(/), query(?), and fragment(#) + std::string path = uri.Resource(); + std::string query = ""; + std::string fragment = ""; + // Extract Path + pos = path.find('?'); + if (pos <= path.size()) { + query = path.substr(pos); // Not removing the leading "?". Query must have the leading "?" if it's not empty (according to JPCSP) + if (query.size() == 1) + query.clear(); + path.erase(pos); + } + // Extract Query and Fragment + pos = query.find('#'); + if (pos <= query.size()) { + fragment = query.substr(pos); // FIXME: Should we remove the leading "#" ? probably not, just like query + query.erase(pos); + } + + // FIXME: Related to "scheme-specific-part" (ie. parts after the scheme + ":") ? 0 = start with "//" + pos = std::string(url).find("://"); + parsedUri->noSlash = (pos != std::string::npos) ? 0 : 1; + + // Store the URI components in sequence into workAreanand store the respective addresses into the parsedUri structure. + int offset = 0; + parsedUri->schemeAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, uri.Protocol().c_str()); // FiXME: scheme in lowercase, while protocol in uppercase? + + parsedUri->userInfoUserNameAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, userInfoUserName.c_str()); + + parsedUri->userInfoPasswordAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, userInfoPassword.c_str()); + + parsedUri->hostAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, host.c_str()); + + parsedUri->pathAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, path.c_str()); + + parsedUri->queryAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, query.c_str()); + + parsedUri->fragmentAddr = workAreaAddr + offset; + offset = workAreaAddString(workAreaAddr, workAreaSize, offset, fragment.c_str()); + + parsedUri->port = uri.Port(); + memset(parsedUri->unknown, 0, sizeof(parsedUri->unknown)); + parsedUri.NotifyWrite("UriParse"); + + Memory::WriteUnchecked_U32(offset, workAreaSizeAddr); + NotifyMemInfo(MemBlockFlags::WRITE, workAreaSizeAddr, 4, "UriParse"); + + return 0; +} const HLEFunction sceParseUri[] = { {0X49E950EC, nullptr, "sceUriEscape", '?', ""}, {0X062BB07E, nullptr, "sceUriUnescape", '?', ""}, - {0X568518C9, nullptr, "sceUriParse", '?', ""}, + {0X568518C9, &WrapI_UCUUI, "sceUriParse", 'i', "xsxxi" }, {0X7EE318AF, nullptr, "sceUriBuild", '?', ""}, }; void Register_sceParseUri() { RegisterModule("sceParseUri", ARRAY_SIZE(sceParseUri), sceParseUri); -} \ No newline at end of file +} diff --git a/Core/HLE/sceParseUri.h b/Core/HLE/sceParseUri.h index 5108cd021986..9b3bb7d06b6a 100644 --- a/Core/HLE/sceParseUri.h +++ b/Core/HLE/sceParseUri.h @@ -17,4 +17,28 @@ #pragma once -void Register_sceParseUri(); \ No newline at end of file +#ifdef _MSC_VER +#define PACK // on MSVC we use #pragma pack() instead so let's kill this. +#pragma pack(push, 1) +#else +#define PACK __attribute__((packed)) +#endif + +typedef struct PSPParsedUri { + s32 noSlash; + s32 schemeAddr; + s32 userInfoUserNameAddr; + s32 userInfoPasswordAddr; + s32 hostAddr; + s32 pathAddr; + s32 queryAddr; + s32 fragmentAddr; + u16 port; + u8 unknown[10]; // padding might be included here? +} PACK PSPParsedUri; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +void Register_sceParseUri();