Files
ppsspp/Core/HLE/sceParseUri.cpp
T
Henrik RydgårdandClaude Opus 5.5 7dd1475ed7 HLE: Pass the MIPSState to syscalls and their implementations
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.

The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.

The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:45:14 +02:00

349 lines
12 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#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 can be 0/null? LittelBigPlanet seems to set workAreaSizeAddr to 0
static int sceUriParse(MIPSState *mips, u32 parsedUriAreaAddr, const char* url, u32 workAreaAddr, u32 workAreaSizeAddr, int workAreaSize) {
WARN_LOG(Log::sceNet, "UNTESTED sceUriParse(%x, %s, %x, %x, %d) at %08x", parsedUriAreaAddr, safe_string(url), workAreaAddr, workAreaSizeAddr, workAreaSize, mips->pc);
if (url == nullptr)
return hleLogError(Log::sceNet, -1, "invalid arg");
auto workAreaSz = PSPPointer<u32>::Create(workAreaSizeAddr);
// Size query. sceUriParse in libparse_uri.prx (6.60) only measures when either area is missing,
// and succeeds as long as it has somewhere to put the size.
if (parsedUriAreaAddr == 0 || workAreaAddr == 0) {
if (workAreaSizeAddr == 0)
return hleLogError(Log::sceNet, 0x804331FE, "no work area and no size pointer");
// Based on JPCSP: The required workArea size is maximum the size of the URL + 7 times the null-byte for string termination.
int sz = (int)strlen(url) + 7;
if (workAreaSz.IsValid()) {
*workAreaSz = sz;
workAreaSz.NotifyWrite("UriParse");
}
return hleLogDebug(Log::sceNet, 0, "workAreaSize: %d, %d", sz, workAreaSize);
}
auto parsedUri = PSPPointer<PSPParsedUri>::Create(parsedUriAreaAddr);
// 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(Log::sceNet, -1, "invalid arg");
}
// Parse Host() into userInfo (in the format "<userName>:<password>") 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("://");
if (parsedUri.IsValid())
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;
if (parsedUri.IsValid())
parsedUri->schemeAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, uri.Protocol().c_str()); // FiXME: scheme in lowercase, while protocol in uppercase?
if (parsedUri.IsValid())
parsedUri->userInfoUserNameAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, userInfoUserName.c_str());
if (parsedUri.IsValid())
parsedUri->userInfoPasswordAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, userInfoPassword.c_str());
if (parsedUri.IsValid())
parsedUri->hostAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, host.c_str());
if (parsedUri.IsValid())
parsedUri->pathAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, path.c_str());
if (parsedUri.IsValid())
parsedUri->queryAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, query.c_str());
if (parsedUri.IsValid())
parsedUri->fragmentAddr = workAreaAddr + offset;
offset = workAreaAddString(workAreaAddr, workAreaSize, offset, fragment.c_str());
if (parsedUri.IsValid()) {
parsedUri->port = uri.Port();
memset(parsedUri->unknown, 0, sizeof(parsedUri->unknown));
parsedUri.NotifyWrite("UriParse");
}
// Update the size
if (workAreaSz.IsValid()) {
*workAreaSz = offset;
workAreaSz.NotifyWrite("UriParse");
}
return 0;
}
static int sceUriBuild(MIPSState *mips, 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, mips->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, 0, "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(MIPSState *mips, 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), mips->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 hleLogDebug(Log::sceNet, 0, "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(), (u32)escaped.length() + 1);
return hleLogDebug(Log::sceNet, 0, "escaped '%s' to '%s'", source, escaped.c_str());
}
const HLEFunction sceParseUri[] =
{
{0X49E950EC, &WrapI_UUIC<sceUriEscape>, "sceUriEscape", 'i', "xxis"},
{0X062BB07E, nullptr, "sceUriUnescape", '?', ""},
{0X568518C9, &WrapI_UCUUI<sceUriParse>, "sceUriParse", 'i', "xsxxi" },
{0X7EE318AF, &WrapI_UUIUI<sceUriBuild>, "sceUriBuild", 'i', "xxxxi" },
};
void Register_sceParseUri()
{
RegisterHLEModule("sceParseUri", ARRAY_SIZE(sceParseUri), sceParseUri);
}