Merge pull request #20670 from cipherxof/portableops

Support for PortableOps Infrastructure
This commit is contained in:
Henrik Rydgård
2025-07-31 22:28:17 +02:00
committed by GitHub
8 changed files with 293 additions and 66 deletions
+3 -1
View File
@@ -63,8 +63,10 @@ bool Connection::Resolve(const char *host, int port, DNSType type) {
char port_str[16];
snprintf(port_str, sizeof(port_str), "%d", port);
std::string processedHostname = ProcessHostnameWithInfraDNS(std::string(host));
std::string err;
if (!net::DNSResolve(host, port_str, &resolved_, err, type)) {
if (!net::DNSResolve(processedHostname.c_str(), port_str, &resolved_, err, type)) {
WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type));
// Zero port so that future calls fail.
port_ = 0;
+51
View File
@@ -1,3 +1,5 @@
#include "Core/Config.h"
#include "Core/HLE/sceNet.h"
#include "ppsspp_config.h"
#include "Common/Net/Resolve.h"
@@ -502,4 +504,53 @@ bool DirectDNSLookupIPV4(const char *dns_server_ip, const char *domain, uint32_t
return true;
}
std::string ProcessHostnameWithInfraDNS(const std::string& hostname) {
std::string resolvedHostname = hostname;
// Resolve any aliases. First check the ini file, then check the hardcoded DNS config.
auto aliasIter = g_Config.mHostToAlias.find(hostname);
if (aliasIter != g_Config.mHostToAlias.end()) {
const std::string& alias = aliasIter->second;
INFO_LOG(Log::sceNet, "%s - Resolved alias %s from hostname %s", __FUNCTION__, alias.c_str(), hostname.c_str());
resolvedHostname = alias;
}
if (g_Config.bInfrastructureAutoDNS) {
// Also look up into the preconfigured fixed DNS JSON.
auto fixedDNSIter = GetInfraDNSConfig().fixedDNS.find(resolvedHostname);
if (fixedDNSIter != GetInfraDNSConfig().fixedDNS.end()) {
const std::string& domainIP = fixedDNSIter->second;
INFO_LOG(Log::sceNet, "%s - Resolved IP %s from fixed DNS lookup with '%s'", __FUNCTION__, domainIP.c_str(), resolvedHostname.c_str());
resolvedHostname = domainIP;
}
}
// Check if hostname is already an IPv4 address, if so we do not need further lookup. This usually happens
// after the mHostToAlias or fixedDNSIter lookups, which effectively both are hardcoded DNS.
uint32_t resolvedAddr;
if (inet_pton(AF_INET, resolvedHostname.c_str(), &resolvedAddr)) {
INFO_LOG(Log::sceNet, "Not looking up '%s', already an IP address.", resolvedHostname.c_str());
return resolvedHostname;
}
// Now use the configured primary DNS server to do a lookup.
// If auto DNS, use the server from that config.
std::string dnsServer;
if (g_Config.bInfrastructureAutoDNS && !GetInfraDNSConfig().dns.empty()) {
dnsServer = GetInfraDNSConfig().dns;
} else {
dnsServer = g_Config.sInfrastructureDNSServer;
}
if (net::DirectDNSLookupIPV4(dnsServer.c_str(), resolvedHostname.c_str(), &resolvedAddr)) {
char temp[32];
inet_ntop(AF_INET, &resolvedAddr, temp, sizeof(temp));
INFO_LOG(Log::sceNet, "Direct lookup of '%s' from '%s' succeeded: %s", resolvedHostname.c_str(), dnsServer.c_str(), temp);
return std::string(temp);
}
WARN_LOG(Log::sceNet, "Direct DNS lookup of '%s' at DNS server '%s' failed. Will try OS DNS...", resolvedHostname.c_str(), dnsServer.c_str());
return resolvedHostname;
}
} // namespace net
+1
View File
@@ -26,5 +26,6 @@ int inet_pton(int af, const char* src, void* dst);
// Does a DNS lookup without involving the OS, so you can hit any DNS server.
bool DirectDNSLookupIPV4(const char *dnsServer, const char *host, uint32_t *ipv4_addr);
std::string ProcessHostnameWithInfraDNS(const std::string& hostname);
} // namespace net
+10
View File
@@ -242,6 +242,16 @@ template<int func(u32, int, const char*, u32, u32)> void WrapI_UICUU() {
RETURN(retval);
}
template<int func(u32, u32, int, const char*)> void WrapI_UUIC() {
int retval = func(PARAM(0), PARAM(1), PARAM(2), Memory::GetCharPointer(PARAM(3)));
RETURN(retval);
}
template<int func(u32, u32, int, u32, int)> void WrapI_UUIUI() {
int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4));
RETURN(retval);
}
template<int func()> void WrapI_V() {
int retval = func();
RETURN(retval);
-2
View File
@@ -747,8 +747,6 @@ static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int e
if (!baseURL.Valid())
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url");
// TODO: Here we should look up baseURL.Host() in DNS.
httpObjects.emplace_back(std::make_shared<HTTPConnection>(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive));
int retid = (int)httpObjects.size();
return hleLogDebug(Log::sceNet, retid);
+9 -60
View File
@@ -86,72 +86,23 @@ static int sceNetResolverTerm() {
// Note: timeouts are in seconds
static int NetResolver_StartNtoA(NetResolver *resolver, u32 hostnamePtr, u32 inAddrPtr, int timeout, int retry) {
addrinfo *resolved = nullptr;
std::string err;
std::string hostname = std::string(safe_string(Memory::GetCharPointer(hostnamePtr)));
SockAddrIN4 addr{};
addr.in.sin_addr.s_addr = INADDR_NONE;
// Resolve any aliases. First check the ini file, then check the hardcoded DNS config.
auto aliasIter = g_Config.mHostToAlias.find(hostname);
if (aliasIter != g_Config.mHostToAlias.end()) {
const std::string& alias = aliasIter->second;
INFO_LOG(Log::sceNet, "%s - Resolved alias %s from hostname %s", __FUNCTION__, alias.c_str(), hostname.c_str());
hostname = alias;
}
if (g_Config.bInfrastructureAutoDNS) {
// Also look up into the preconfigured fixed DNS JSON.
auto fixedDNSIter = GetInfraDNSConfig().fixedDNS.find(hostname);
if (fixedDNSIter != GetInfraDNSConfig().fixedDNS.end()) {
const std::string& domainIP = fixedDNSIter->second;
INFO_LOG(Log::sceNet, "%s - Resolved IP %s from fixed DNS lookup with '%s'", __FUNCTION__, domainIP.c_str(), hostname.c_str());
hostname = domainIP;
}
}
// Check if hostname is already an IPv4 address, if so we do not need further lookup. This usually happens
// after the mHostToAlias or fixedDNSIter lookups, which effectively both are hardcoded DNS.
uint32_t resolvedAddr;
if (inet_pton(AF_INET, hostname.c_str(), &resolvedAddr)) {
INFO_LOG(Log::sceNet, "Not looking up '%s', already an IP address.", hostname.c_str());
Memory::Write_U32(resolvedAddr, inAddrPtr);
return 0;
}
// Process hostname with infra-DNS
std::string processedHostname = net::ProcessHostnameWithInfraDNS(hostname);
// Flag resolver as in-progress - not relevant for sync functions but potentially relevant for async
resolver->isRunning = true;
// Now use the configured primary DNS server to do a lookup.
// If auto DNS, use the server from that config.
std::string dnsServer;
if (g_Config.bInfrastructureAutoDNS && !GetInfraDNSConfig().dns.empty()) {
dnsServer = GetInfraDNSConfig().dns;
} else {
dnsServer = g_Config.sInfrastructureDNSServer;
}
if (net::DirectDNSLookupIPV4(dnsServer.c_str(), hostname.c_str(), &resolvedAddr)) {
char temp[32];
inet_ntop(AF_INET, &resolvedAddr, temp, sizeof(temp));
addrinfo *resolved = nullptr;
std::string err;
if (!net::DNSResolve(processedHostname, "", &resolved, err)) {
ERROR_LOG(Log::sceNet, "OS DNS Error Resolving %s (%s)", processedHostname.c_str(), err.c_str());
resolver->isRunning = false;
Memory::Write_U32(resolvedAddr, inAddrPtr);
INFO_LOG(Log::sceNet, "Direct lookup of '%s' from '%s' succeeded: %s (%08x)", hostname.c_str(), dnsServer.c_str(), temp, resolvedAddr);
return 0;
}
WARN_LOG(Log::sceNet, "Direct DNS lookup of '%s' at DNS server '%s' failed. Trying OS DNS...", hostname.c_str(), g_Config.sInfrastructureDNSServer.c_str());
// Attempt to execute an OS DNS resolution
if (!net::DNSResolve(hostname, "", &resolved, err)) {
// TODO: Return an error based on the outputted "err" (unfortunately it's already converted to string)
ERROR_LOG(Log::sceNet, "OS DNS Error Resolving %s (%s)\n", hostname.c_str(), err.c_str());
return SCE_NET_RESOLVER_ERROR_INVALID_HOST;
}
// If successful, write to memory
// Process results
SockAddrIN4 addr{};
if (resolved) {
for (auto ptr = resolved; ptr != nullptr; ptr = ptr->ai_next) {
switch (ptr->ai_family) {
@@ -161,9 +112,7 @@ static int NetResolver_StartNtoA(NetResolver *resolver, u32 hostnamePtr, u32 inA
}
}
net::DNSResolveFree(resolved);
Memory::Write_U32(addr.in.sin_addr.s_addr, inAddrPtr);
resolver->isRunning = false;
INFO_LOG(Log::sceNet, "%s - Hostname: %s => IPv4: %s", __FUNCTION__, hostname.c_str(),
ip2str(addr.in.sin_addr, false).c_str());
}
+181 -3
View File
@@ -54,7 +54,7 @@ static int sceUriParse(u32 parsedUriAreaAddr, const char* url, u32 workAreaAddr,
*workAreaSz = sz;
workAreaSz.NotifyWrite("UriParse");
}
return hleLogDebug(Log::sceNet, 0, "workAreaSize: %d, %d", sz, workAreaSize);
return hleLogDebug(Log::sceNet, -1, "workAreaSize: %d, %d", sz, workAreaSize);
}
auto parsedUri = PSPPointer<PSPParsedUri>::Create(parsedUriAreaAddr);
@@ -153,12 +153,190 @@ static int sceUriParse(u32 parsedUriAreaAddr, const char* url, u32 workAreaAddr,
return 0;
}
static int sceUriBuild(u32 workAreaAddr, u32 workAreaSizeAddr, int workAreaSize, u32 parsedUriAddr, int flags) {
WARN_LOG(Log::sceNet, "UNTESTED sceUriBuild(%x, %x, %d, %x, %d) at %08x", workAreaAddr, workAreaSizeAddr, workAreaSize, parsedUriAddr, flags, currentMIPS->pc);
auto workAreaSz = PSPPointer<u32>::Create(workAreaSizeAddr);
if (parsedUriAddr == 0) {
return hleLogError(Log::sceNet, -1, "invalid parsedUriAddr");
}
auto parsedUri = PSPPointer<PSPParsedUri>::Create(parsedUriAddr);
if (!parsedUri.IsValid()) {
return hleLogError(Log::sceNet, -1, "invalid parsedUri structure");
}
auto getUriComponent = [](u32 addr, int flags, int flagMask) -> std::string {
if (addr == 0 || (flags & flagMask) == 0) return "";
const char* str = Memory::GetCharPointer(addr);
return str ? std::string(str) : "";
};
std::string scheme = getUriComponent(parsedUri->schemeAddr, flags, 0x1);
std::string userInfoUserName = getUriComponent(parsedUri->userInfoUserNameAddr, flags, 0x10);
std::string userInfoPassword = getUriComponent(parsedUri->userInfoPasswordAddr, flags, 0x20);
std::string host = getUriComponent(parsedUri->hostAddr, flags, 0x2);
std::string path = getUriComponent(parsedUri->pathAddr, flags, 0x8);
std::string query = getUriComponent(parsedUri->queryAddr, flags, 0x40);
std::string fragment = getUriComponent(parsedUri->fragmentAddr, flags, 0x80);
int port = (flags & 0x4) != 0 ? parsedUri->port : -1;
// Build the complete URI
std::string uri = "";
if (!scheme.empty()) {
std::string finalScheme = scheme;
if (finalScheme == "https") { // https is unsupported
finalScheme = "http";
}
uri += finalScheme + ":";
}
if (parsedUri->noSlash == 0) {
uri += "//";
}
if (!userInfoUserName.empty()) {
uri += userInfoUserName;
if (!userInfoPassword.empty()) {
uri += ":" + userInfoPassword;
}
uri += "@";
}
if (!host.empty()) {
uri += host;
}
if (port > 0) {
int defaultPort = -1;
if (parsedUri->schemeAddr != 0) {
const char* protocol = Memory::GetCharPointer(parsedUri->schemeAddr);
if (protocol) {
std::string protocolStr(protocol);
if (protocolStr == "http") defaultPort = 80;
//else if (protocolStr == "https") defaultPort = 443;
else if (protocolStr == "ftp") defaultPort = 21;
else if (protocolStr == "ssh") defaultPort = 22;
}
}
if (port != defaultPort) {
uri += ":" + std::to_string(port);
}
}
if (!path.empty()) {
uri += path;
}
if (!query.empty()) {
uri += query;
}
if (!fragment.empty()) {
uri += fragment;
}
int requiredSize = (int)uri.length() + 1;
if (workAreaSz.IsValid()) {
*workAreaSz = requiredSize;
workAreaSz.NotifyWrite("UriBuild");
}
if (workAreaAddr == 0) {
return hleLogDebug(Log::sceNet, -1, "size query, required size: %d", requiredSize);
}
if (requiredSize > workAreaSize) {
return hleLogError(Log::sceNet, -1, "work area too small: need %d, have %d", requiredSize, workAreaSize);
}
Memory::MemcpyUnchecked(workAreaAddr, uri.c_str(), requiredSize);
return hleLogDebug(Log::sceNet, 0, "built URI: %s", uri.c_str());
}
static int sceUriEscape(u32 escapedAddr, u32 escapedLengthAddr, int escapedBufferLength, const char* source) {
WARN_LOG(Log::sceNet, "UNTESTED sceUriEscape(%x, %x, %d, %s) at %08x", escapedAddr, escapedLengthAddr, escapedBufferLength, safe_string(source), currentMIPS->pc);
if (source == nullptr) {
return hleLogError(Log::sceNet, -1, "invalid source");
}
auto escapedLength = PSPPointer<u32>::Create(escapedLengthAddr);
// Helper function to check if a character needs to be escaped
auto needsEscaping = [](unsigned char c) -> bool {
// Characters that should NOT be escaped (unreserved characters in RFC 3986)
// ALPHA / DIGIT / "-" / "." / "_" / "~"
if ((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' || c == '.' || c == '_' || c == '~') {
return false;
}
// Everything else needs to be escaped
return true;
};
// Helper function to convert a hex digit to character
auto toHexChar = [](int digit) -> char {
if (digit < 10) return '0' + digit;
return 'A' + (digit - 10);
};
std::string sourceStr(source);
int requiredSize = 0;
for (unsigned char c : sourceStr) {
if (needsEscaping(c)) {
requiredSize += 3; // %XX format
} else {
requiredSize += 1; // unchanged character
}
}
requiredSize += 1; // null terminator
if (escapedLength.IsValid()) {
*escapedLength = requiredSize;
escapedLength.NotifyWrite("UriEscape");
}
if (escapedAddr == 0) {
return hleLogError(Log::sceNet, -1, "size query, required size: %d", requiredSize);
}
if (requiredSize > escapedBufferLength) {
return hleLogError(Log::sceNet, -1, "buffer too small: need %d, have %d", requiredSize, escapedBufferLength);
}
std::string escaped = "";
for (unsigned char c : sourceStr) {
if (needsEscaping(c)) {
// Escape as %XX
escaped += '%';
escaped += toHexChar((c >> 4) & 0xF);
escaped += toHexChar(c & 0xF);
} else {
// Keep unchanged
escaped += c;
}
}
Memory::MemcpyUnchecked(escapedAddr, escaped.c_str(), escaped.length() + 1);
return hleLogDebug(Log::sceNet, 0, "escaped '%s' to '%s'", source, escaped.c_str());
}
const HLEFunction sceParseUri[] =
{
{0X49E950EC, nullptr, "sceUriEscape", '?', ""},
{0X49E950EC, &WrapI_UUIC<sceUriEscape>, "sceUriEscape", 'i', "xxis"},
{0X062BB07E, nullptr, "sceUriUnescape", '?', ""},
{0X568518C9, &WrapI_UCUUI<sceUriParse>, "sceUriParse", 'i', "xsxxi" },
{0X7EE318AF, nullptr, "sceUriBuild", '?', ""},
{0X7EE318AF, &WrapI_UUIUI<sceUriBuild>, "sceUriBuild", 'i', "xxxxi" },
};
void Register_sceParseUri()
+38
View File
@@ -91,6 +91,44 @@
"ULJM05301"
]
},
{
"name": "Portable Ops",
"comment": {
"en_US": "Support for MPO and MPO+."
},
"revival_credits": {
"group": "SaveMGO",
"url": "https://discord.gg/mgo2pc"
},
"dns": "mpo.savemgo.com",
"dyn_dns": "mpo.savemgo.com",
"domains": {
"mpoweb.konamionline.com": "mpo.savemgo.com",
"info.service.konamionline.com": "mpo.savemgo.com",
"mpostunus.konamionline.com": "stun.mgo2pc.com",
"mpostunjp.konamionline.com": "stun.mgo2pc.com",
"mpostuneu.konamionline.com": "stun.mgo2pc.com"
},
"score": 4,
"known_working_ids": [
"ULJM05261",
"ULJM05284",
"ULJM05284",
"ULUS10290",
"ULAS42123",
"ULES01003",
"ULUS10202",
"ULJM05193",
"VP033J1",
"ULKS46119",
"ULES00645",
"ULJM05256",
"ULJM08016",
"ULUS10202GH",
"NPJH50112",
"ULJM05573"
]
},
{
"name": "Need for Speed: Undercover",
"comment": {