libNet: honour the option mask in sceHttpUriBuild (#1069)

* libNet: honour the option mask in sceHttpUriBuild

sceHttpUriBuild's `option` argument (SCE_HTTP_URI_BUILD_WITH_*) selects which
components of the SceHttpUriElement end up in the output. HttpUriBuild logged
the value and ignored it, always returning the full URL.

Black Myth: Wukong calls it with SCHEME alone and requires "https://" (or
"http://") back before it starts an HTTP request. The full URL fails that
check, so the request starter returns false. The caller's "run on game thread
and wait" helper then returns early while its task stays queued, still holding
by-reference captures of a stack frame that is already gone. About 70 seconds
into boot the game thread runs that task against the dead stack and faults
(null read at eboot+0x59662f). With the mask honoured the game gets past that
point and reaches its title cinematic and menu.

Bits: SCHEME 0x01, HOSTNAME 0x02, PORT 0x04, PATH 0x08, USERNAME 0x10,
PASSWORD 0x20, QUERY 0x40, FRAGMENT 0x80. A mask with none of the eight bits
set keeps the previous "everything" behaviour for callers that never pass one.
A scheme-only build returns "https://", the authority marker belonging to the
scheme chunk.

Adds TestBuildHonoursOption to http_uri_parse covering scheme-only, hostname,
port, path, and the 0xff / 0 cases.

* libNet: simplify URI build code and tests

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
Rohit Mahto 2026-10-05 23:01:45 +05:30 committed by GitHub
parent e6fce45d3a
commit dc3cd2d2c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 113 additions and 32 deletions

View File

@ -599,34 +599,66 @@ static int KYTY_SYSV_ABI HttpUriBuild(char* out, size_t* require, size_t prepare
return HTTP_ERROR_INVALID_VALUE;
}
std::string uri;
if (src_element->scheme != nullptr) {
uri.append(src_element->scheme);
uri.push_back(':');
}
constexpr uint32_t BUILD_WITH_SCHEME = 0x01;
constexpr uint32_t BUILD_WITH_HOSTNAME = 0x02;
constexpr uint32_t BUILD_WITH_PORT = 0x04;
constexpr uint32_t BUILD_WITH_PATH = 0x08;
constexpr uint32_t BUILD_WITH_USERNAME = 0x10;
constexpr uint32_t BUILD_WITH_PASSWORD = 0x20;
constexpr uint32_t BUILD_WITH_QUERY = 0x40;
constexpr uint32_t BUILD_WITH_FRAGMENT = 0x80;
constexpr uint32_t BUILD_WITH_ALL = 0xff;
if (src_element->opaque == 0) {
if (src_element->hostname != nullptr) {
// No component bits preserves the legacy full-URI build.
const uint32_t parts = (option & BUILD_WITH_ALL) == 0 ? BUILD_WITH_ALL : option;
const bool hierarchical = src_element->opaque == 0 && src_element->hostname != nullptr;
const bool with_hostname = (parts & BUILD_WITH_HOSTNAME) != 0 && hierarchical;
std::string uri;
if ((parts & BUILD_WITH_SCHEME) != 0) {
if (src_element->scheme != nullptr) {
uri.append(src_element->scheme);
uri.push_back(':');
}
// Scheme-only builds include the authority marker.
if (hierarchical) {
uri.append("//");
if (src_element->username != nullptr) {
uri.append(src_element->username);
if (src_element->password != nullptr) {
uri.push_back(':');
uri.append(src_element->password);
}
uri.push_back('@');
}
uri.append(src_element->hostname);
if (src_element->port != 0) {
uri.push_back(':');
uri.append(std::to_string(src_element->port));
}
}
}
AppendUriPart(&uri, src_element->path);
AppendUriPart(&uri, src_element->query);
AppendUriPart(&uri, src_element->fragment);
const bool with_username = (parts & BUILD_WITH_USERNAME) != 0 && src_element->username != nullptr;
const bool with_password = (parts & BUILD_WITH_PASSWORD) != 0 && src_element->password != nullptr;
if (with_username) {
uri.append(src_element->username);
}
if (with_password) {
if (with_username) {
uri.push_back(':');
}
uri.append(src_element->password);
}
if (with_hostname) {
if (with_username || with_password) {
uri.push_back('@');
}
uri.append(src_element->hostname);
}
if ((parts & BUILD_WITH_PORT) != 0 && hierarchical && src_element->port != 0) {
if (with_hostname) {
uri.push_back(':');
}
uri.append(std::to_string(src_element->port));
}
if ((parts & BUILD_WITH_PATH) != 0) {
AppendUriPart(&uri, src_element->path);
}
if ((parts & BUILD_WITH_QUERY) != 0) {
AppendUriPart(&uri, src_element->query);
}
if ((parts & BUILD_WITH_FRAGMENT) != 0) {
AppendUriPart(&uri, src_element->fragment);
}
const auto needed = uri.size() + 1;
if (require != nullptr) {

View File

@ -6,6 +6,7 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
namespace Libs::LibHttp {
void InitNet_1_Http(Loader::SymbolDatabase *symbols);
@ -40,14 +41,14 @@ int failures = 0;
using HttpUriParse = int(KYTY_SYSV_ABI *)(SceHttpUriElement *, const char *,
void *, size_t *, size_t);
HttpUriParse GetHttpUriParse() {
Loader::SymbolDatabase symbols;
Libs::LibHttp::InitNet_1_Http(&symbols);
const auto *record =
symbols.FindByNid("IWalAn-guFs", Loader::SymbolType::Func);
using HttpUriBuild = int(KYTY_SYSV_ABI *)(char *, size_t *, size_t,
const SceHttpUriElement *, uint32_t);
template <typename T>
T GetHttpFunction(const Loader::SymbolDatabase &symbols, const char *nid) {
const auto *record = symbols.FindByNid(nid, Loader::SymbolType::Func);
CHECK(record != nullptr);
return record != nullptr ? reinterpret_cast<HttpUriParse>(record->vaddr)
: nullptr;
return record != nullptr ? reinterpret_cast<T>(record->vaddr) : nullptr;
}
template <size_t N>
@ -117,13 +118,61 @@ void TestPresentQuery(HttpUriParse parse) {
}
}
std::string BuildWith(HttpUriBuild build, const SceHttpUriElement &element,
uint32_t option) {
size_t required = 0;
CHECK(build(nullptr, &required, 0, &element, option) == 0);
std::array<char, 256> out{};
size_t filled = 0;
CHECK(required <= out.size());
CHECK(build(out.data(), &filled, required, &element, option) == 0);
CHECK(filled == required);
return std::string(out.data());
}
void TestBuildHonoursOption(HttpUriParse parse, HttpUriBuild build) {
constexpr char url[] =
"https://user:pw@gssdk1.gamesci.com.cn:8443/VersionServerImpl?x=1#frag";
size_t required = 0;
CHECK(parse(nullptr, url, nullptr, &required, 0) == 0);
std::array<char, 256> pool{};
SceHttpUriElement element{};
CHECK(parse(&element, url, pool.data(), &required, required) == 0);
constexpr uint32_t scheme = 0x01, hostname = 0x02, port = 0x04, path = 0x08,
username = 0x10, password = 0x20, query = 0x40,
fragment = 0x80;
CHECK(BuildWith(build, element, scheme) == "https://");
CHECK(BuildWith(build, element, hostname) == "gssdk1.gamesci.com.cn");
CHECK(BuildWith(build, element, port) == "8443");
CHECK(BuildWith(build, element, path) == "/VersionServerImpl");
CHECK(BuildWith(build, element, username) == "user");
CHECK(BuildWith(build, element, password) == "pw");
CHECK(BuildWith(build, element, query) == "?x=1");
CHECK(BuildWith(build, element, fragment) == "#frag");
CHECK(BuildWith(build, element, scheme | hostname) ==
"https://gssdk1.gamesci.com.cn");
CHECK(BuildWith(build, element, hostname | path) ==
"gssdk1.gamesci.com.cn/VersionServerImpl");
CHECK(BuildWith(build, element, hostname | port) ==
"gssdk1.gamesci.com.cn:8443");
CHECK(BuildWith(build, element, 0xff) == url);
CHECK(BuildWith(build, element, 0) == url);
}
} // namespace
int main() {
const auto parse = GetHttpUriParse();
if (parse == nullptr) {
Loader::SymbolDatabase symbols;
Libs::LibHttp::InitNet_1_Http(&symbols);
const auto parse = GetHttpFunction<HttpUriParse>(symbols, "IWalAn-guFs");
const auto build = GetHttpFunction<HttpUriBuild>(symbols, "5LZA+KPISVA");
if (parse == nullptr || build == nullptr) {
return 1;
}
TestBuildHonoursOption(parse, build);
TestAbsentQuery(parse);
TestEmptyUri(parse);
TestPresentQuery(parse);